// // tools/audit-verify.c
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <string.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <sys/stat.h>

// #include <openssl/evp.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "alarm.c" // 引入报警功能
// #include "../../lib/gm_crypto.h"

// #define SM2_ID "1234567812345678"

// //---添加被篡改行列表结构与工具函数---//
// typedef struct
// {
//     unsigned long long *lines;
//     size_t count;
//     size_t cap;
// } BadList;

// static void badlist_init(BadList *b1)
// {
//     b1->lines = NULL;
//     b1->count = 0;
//     b1->cap = 0;
// }

// static void badlist_free(BadList *b1)
// {
//     free(b1->lines);
//     b1->lines = NULL;
//     b1->count = b1->cap = 0;
// }

// static void badlist_add(BadList *b1, unsigned long long ln)
// {
//     if (b1->count == b1->cap)
//     {
//         size_t nc = b1->cap ? b1->cap * 2 : 64;
//         unsigned long long *p = realloc(b1->lines, nc * sizeof(*p));
//         if (!p)
//             return;
//         b1->lines = p;
//         b1->cap = nc;
//     }
//     b1->lines[b1->count++] = ln;
// }
// //-----------------------------------//

// static void diee(const char *m)
// {
//     fprintf(stderr, "%s\n", m);
//     exit(1);
// }

// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//     {
//         fclose(f);
//         return -1;
//     }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         fclose(f);
//         return -1;
//     }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
//     {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(f);
//             return -1;
//         }
//     }
//     fclose(f);
//     unsigned char md[32];
//     unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     static const char hex[] = "0123456789abcdef";
//     for (int i = 0; i < 32; i++)
//     {
//         out_hex[i * 2] = hex[(md[i] >> 4) & 0xF];
//         out_hex[i * 2 + 1] = hex[md[i] & 0xF];
//     }
//     out_hex[64] = 0;
//     return 0;
// }

// static unsigned char *b64_decode(const char *b64, size_t *out_len)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m)
//     {
//         if (b)
//             BIO_free(b);
//         if (m)
//             BIO_free(m);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0)
//     {
//         free(buf);
//         return NULL;
//     }
//     *out_len = (size_t)n;
//     return buf;
// }

// //---支持记录每一行被篡改的日志---//

// /*
// 对比日志与索引：扫描每一行，校验行哈希（lhash）,并把所有篡改行加入 out_bad。
// 返回值：篡改的行总数。出错时返回 (unsigned long long)-1。
// scanned_lines 输出实际扫描的行数。
// */
// static unsigned long long compare_with_index(const char *log_path, const char *idx_path,
//                                              BadList *out_bad,
//                                              unsigned long long *scanned_lines)
// {
//     *scanned_lines = 0;
//     if (out_bad)
//         badlist_init(out_bad);
//     FILE *lf = fopen(log_path, "rb");
//     FILE *ix = fopen(idx_path, "r");
//     if (!lf || !ix)
//     {
//         if (lf)
//             fclose(lf);
//         if (ix)
//             fclose(ix);
//         return (unsigned long long)-1;
//     }

//     // 跳过索引头（直到遇到以 i= 开始的行）
//     char buf[1024];
//     long back_pos = 0;
//     while (fgets(buf, sizeof(buf), ix))
//     {
//         if (buf[0] == '#')
//             continue;
//         if (strncmp(buf, "i=", 2) == 0)
//         {
//             back_pos = ftell(ix) - (long)strlen(buf);
//             break;
//         }
//     }
//     if (back_pos > 0)
//         fseek(ix, back_pos, SEEK_SET);

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//     {
//         fclose(lf);
//         fclose(ix);
//         return (unsigned long long)-1;
//     }
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     unsigned long long bad = 0;

//     static const char hex[] = "0123456789abcdef";
//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;

//         // 读索引一行：i=<n> off=<off> lhash=<hex> chash=<hex>
//         if (!fgets(buf, sizeof(buf), ix))
//         {
//             // 索引过短，视为被篡改
//             bad++;
//             if (out_bad)
//                 badlist_add(out_bad, ln);
//             continue;
//         }

//         unsigned long long idx_ln = 0, off = 0;
//         char lhex[128], chex[128];
//         if (sscanf(buf, "i=%llu off=%llu lhash=%64s chash=%64s", &idx_ln, &off, lhex, chex) != 4 || idx_ln != ln)
//         {
//             bad++;
//             if (out_bad)
//                 badlist_add(out_bad, ln);
//             continue;
//         }

//         // 计算当前行的 lhash
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32];
//         unsigned int lm = 0;
//         int ok1 = c1 &&
//                   EVP_DigestInit_ex(c1, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(c1, line, (size_t)n) == 1 &&
//                   EVP_DigestFinal_ex(c1, L, &lm) == 1 && lm == 32;
//         if (!ok1)
//         {
//             if (c1)
//                 EVP_MD_CTX_free(c1);
//             fclose(lf);
//             fclose(ix);
//             free(line);
//             return (unsigned long long)-1;
//         }
//         EVP_MD_CTX_free(c1);

//         // 转 hex
//         char Lhex[65];
//         for (int i = 0; i < 32; i++)
//         {
//             Lhex[i * 2] = hex[(L[i] >> 4) & 0xF];
//             Lhex[i * 2 + 1] = hex[L[i] & 0xF];
//         }
//         Lhex[64] = 0;

//         // 对比 lhash
//         if (strcmp(Lhex, lhex) != 0)
//         {
//             bad++;
//             if (out_bad)
//                 badlist_add(out_bad, ln);
//         }
//     }
//     free(line);
//     fclose(lf);
//     fclose(ix);

//     *scanned_lines = ln;
//     return bad;
// }

// static int sm3_chain_file(const char *path, unsigned char out32[32], unsigned long long *out_lines)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     unsigned char H[32] = {0};
//     unsigned long long lines = 0;

//     FILE *fp = fopen(path, "rb");
//     if (!fp)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     while ((n = getline(&line, &cap, fp)) != -1)
//     {
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx)
//         {
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         if (EVP_DigestUpdate(ctx, line, (size_t)n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         unsigned int mdlen = 0;
//         if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);
//         lines++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     if (out_lines)
//         *out_lines = lines;
//     return 0;
// }

// /* 解析 seal（简易解析器，按 key=value） */
// struct seal
// {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// //-------------------------------------
// static char *trimws(char *s)
// {
//     while (*s == ' ' || *s == '\t')
//         s++;
//     size_t n = strlen(s);
//     while (n > 0 && (s[n - 1] == ' ' || s[n - 1] == '\t' || s[n - 1] == '\r' || s[n - 1] == '\n'))
//         s[--n] = 0;
//     return s;
// }
// //-------------------------------------

// static void trimnl(char *s)
// {
//     size_t n = strlen(s);
//     while (n && (s[n - 1] == '\n' || s[n - 1] == '\r'))
//         s[--n] = 0;
// }
// static int parse_seal(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1)
//     {
//         if (line[0] == '#' || line[0] == 0)
//             continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq)
//             continue;
//         //*eq=0; char *k=line; char *v=eq+1;
//         //---------------
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq + 1);

//         // 添加调式输出
//         fprintf(stderr, "[DEBUG] Parsing: '%s' = '%s'\n", k, v);
//         //---------------
//         if (strcmp(k, "version") == 0)
//             S->version = strdup(v);
//         else if (strcmp(k, "hash") == 0)
//             S->hash = strdup(v);
//         else if (strcmp(k, "sign") == 0)
//             S->sign = strdup(v);
//         else if (strcmp(k, "sm2_id") == 0)
//             S->sm2_id = strdup(v);
//         else if (strcmp(k, "path") == 0)
//             S->path = strdup(v);
//         else if (strcmp(k, "records") == 0)
//             S->records = strdup(v);
//         else if (strcmp(k, "chain") == 0)
//             S->chain = strdup(v);
//         else if (strcmp(k, "key_fpr") == 0)
//             S->key_fpr = strdup(v);
//         else if (strcmp(k, "cert_der_base64") == 0)
//             S->cert_b64 = strdup(v);
//         else if (strcmp(k, "sig_base64") == 0)
//             S->sig_b64 = strdup(v);
//         else if (strcmp(k, "index_sm3") == 0)
//             S->index_sm3 = strdup(v);
//     }
//     free(line);
//     fclose(f);
//     return (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) ? 0 : -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version);
//     free(S->hash);
//     free(S->sign);
//     free(S->sm2_id);
//     free(S->path);
//     free(S->records);
//     free(S->chain);
//     free(S->key_fpr);
//     free(S->cert_b64);
//     free(S->sig_b64);
//     free(S->index_sm3);
// }

// static char *build_canonical(const struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//                  "version=%s\n"
//                  "hash=%s\n"
//                  "sign=%s\n"
//                  "sm2_id=%s\n"
//                  "path=%s\n"
//                  "records=%s\n"
//                  "chain=%s\n"
//                  "key_fpr=%s\n"
//                  "index_sm3=%s\n",
//                  S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0)
//         return NULL;
//     return buf;
// }
// //----------------------------------------

// /* 打印 canon 的前缀 hex，方便 seal/verify 比对 */
// static void dump_hex_prefix(const char *tag, const unsigned char *buf, size_t len)
// {
//     size_t n = len > 128 ? 128 : len;
//     fprintf(stderr, "[DBG] %s canon len=%zu hex:", tag, len);
//     for (size_t i = 0; i < n; i++)
//         fprintf(stderr, "%02x", buf[i]);
//     if (len > n)
//         fprintf(stderr, "...");
//     fputc('\n', stderr);
// }

// //----------------------------------------
// int main(int argc, char **argv)
// {
//     if (argc != 2)
//     {
//         fprintf(stderr, "Usage: %s /var/log/audit/audit.log.1.seal\n", argv[0]);
//         return 1;
//     }

//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();
//     if (gm_crypto_init() != 0)
//         diee("gm init failed");
//     if (!EVP_get_digestbyname("sm3"))
//         diee("[ERROR] 无 sm3");

//     struct seal S;
//     if (parse_seal(argv[1], &S) != 0)
//         diee("parse seal failed");

//     if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0)
//         diee("[ERROR] seal 非 sm2/sm3");
//     if (strcmp(S.sm2_id, SM2_ID) != 0)
//         diee("[ERROR] SM2_ID 不匹配");

//     /* 重新计算链值 */
//     unsigned char chain[32];
//     unsigned long long lines = 0;
//     if (sm3_chain_file(S.path, chain, &lines) != 0)
//         diee("recompute chain failed");
//     char chain_hex[65];
//     static const char hex[] = "0123456789abcdef";
//     for (int i = 0; i < 32; i++)
//     {
//         chain_hex[i * 2] = hex[(chain[i] >> 4) & 0xF];
//         chain_hex[i * 2 + 1] = hex[chain[i] & 0xF];
//     }
//     chain_hex[64] = 0;

//     if (strcmp(chain_hex, S.chain) != 0)
//     {
//         // 整体不一致
//         trigger_alarm("Chain mismatch detected: audit log may be tampered!");
//         fprintf(stderr, "[FAIL] chain mismatch (文件被改动?)\n");

//         // 如果 seal 中带了 index_sm3，则进一步校验 idx 并细粒度比对
//         if (S.index_sm3 && *S.index_sm3)
//         {
//             char *idx_path = NULL;
//             if (asprintf(&idx_path, "%s.idx", S.path) < 0)
//                 idx_path = NULL;

//             if (idx_path)
//             {
//                 char idx_sm3_now[65];
//                 if (sm3_file_hex(idx_path, idx_sm3_now) == 0)
//                 {
//                     if (strcmp(idx_sm3_now, S.index_sm3) != 0)
//                     {
//                         trigger_alarm("Index file hash mismatch: index may also be tampered!");
//                         fprintf(stderr, "[ALERT] index_sm3 mismatch: %s (seal) vs %s (current)\n",
//                                 S.index_sm3, idx_sm3_now);
//                     }
//                     else
//                     {
//                         BadList bl;
//                         badlist_init(&bl);
//                         unsigned long long scanned = 0;
//                         unsigned long long bad = compare_with_index(S.path, idx_path, &bl, &scanned);
//                         if (bad == (unsigned long long)-1)
//                         {
//                             fprintf(stderr, "[ERR] failed to compare with index\n");
//                         }
//                         else
//                         {
//                             fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned, bad);
//                             if (bl.count > 0)
//                             {
//                                 fprintf(stderr, "[DETAIL] bad line list: ");
//                                 for (size_t i = 0; i < bl.count; i++)
//                                 {
//                                     fprintf(stderr, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                                 }
//                                 fputc('\n', stderr);

//                                 // 触发报警（含坏行列表），可简化为打印或写文件/发 syslog 等
//                                 char msg[4096];
//                                 size_t pos = 0;
//                                 pos += snprintf(msg + pos, sizeof(msg) - pos, "Tampered lines: ");
//                                 for (size_t i = 0; i < bl.count && pos < sizeof(msg) - 32; i++)
//                                 {
//                                     pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                                 }
//                                 trigger_alarm(msg);
//                             }
//                         }
//                         badlist_free(&bl);
//                     }
//                 }
//                 else
//                 {
//                     fprintf(stderr, "[WARN] cannot compute sm3 of %s\n", idx_path);
//                 }
//                 free(idx_path);
//             }
//         }

//         // 既然 chain mismatch，就不用继续验签了，直接退出
//         free_seal(&S);
//         gm_crypto_cleanup();
//         return 1;
//     }

//     /* 还原证书并取公钥 */
//     size_t cert_len = 0;
//     unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der)
//         diee("cert b64 decode failed");
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert)
//         diee("d2i_X509 failed");
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub)
//         diee("X509_get_pubkey failed");
//     /* 必须是 SM2 曲线 */
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey)
//     {
//         trigger_alarm("Public key is not EC key!");
//         diee("pub is not EC");
//     }
//     const EC_GROUP *grp = EC_KEY_get0_group(eckey);
//     if (!grp || EC_GROUP_get_curve_name(grp) != NID_sm2)
//     {
//         trigger_alarm("Certificate is not SM2 type!");
//         diee("[ERROR] 证书不是SM2");
//     }

//     /* 再计算指纹（SM3）比对 */
//     unsigned char *der2 = NULL;
//     int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0)
//         diee("i2d_X509 failed");
//     unsigned char md[EVP_MAX_MD_SIZE];
//     unsigned int mdlen = 0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//         diee("ctx");
//     if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, md, &mdlen) != 1)
//     {
//         EVP_MD_CTX_free(ctx); //---------------------
//         OPENSSL_free(der2);   //---------------------
//         diee("fingerprint digest failed");
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der2);
//     char fpr_hex[65];
//     for (unsigned i = 0; i < mdlen; i++)
//     {
//         fpr_hex[i * 2] = hex[(md[i] >> 4) & 0xF];
//         fpr_hex[i * 2 + 1] = hex[md[i] & 0xF];
//     }
//     fpr_hex[mdlen * 2] = 0;
//     if (strcmp(fpr_hex, S.key_fpr) != 0)
//     {
//         trigger_alarm("Certificate fingerprint mismatch detected!");
//         diee("[FAIL] key_fpr mismatch");
//     }

//     /* 组装规范串并验签（强制 SM2 控件可用） */
//     char *canon = build_canonical(&S);
//     if (!canon)
//         diee("asprintf");
//     //--------------------

//     dump_hex_prefix("verify", (const unsigned char *)canon, strlen(canon));

//     //--------------------
//     size_t siglen = 0;
//     unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig)
//         diee("sig b64 decode failed");

//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon),
//                           SM2_ID, sig, siglen);
//     free(sig);
//     free(canon);
//     EVP_PKEY_free(pub);
//     X509_free(cert);
//     gm_crypto_cleanup();

//     if (v == 1)
//     {
//         fprintf(stderr, "[OK] verify success\n");
//     }
//     else if (v == 0)
//     {
//         trigger_alarm("Signature mismatch detected!"); // 如果签名无效，触发报警
//         diee("[FAIL] signature invalid");
//     }
//     else
//     {
//         trigger_alarm("Signature verification error!");
//         diee("[FAIL] verify error");
//     }
//     free_seal(&S);
//     return 0;
// }

/* tools/audit-verify.c
   修正版：对 seal.records 做受限链计算；当 audit.log 行数 < seal.records 明确报截断；
   并能用 idx 做逐行比对，列出被篡改行号。
*/

// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <string.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <sys/stat.h>

// #include <openssl/evp.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "alarm.c" // 你已有的 trigger_alarm 实现
// #include "../../lib/gm_crypto.h"

// #define SM2_ID "1234567812345678"
// static const char HEX_CHARS[] = "0123456789abcdef";

// /* BadList (记录被篡改行号) */
// typedef struct
// {
//     unsigned long long *lines;
//     size_t count;
//     size_t cap;
// } BadList;
// static void badlist_init(BadList *b1)
// {
//     b1->lines = NULL;
//     b1->count = 0;
//     b1->cap = 0;
// }
// static void badlist_free(BadList *b1)
// {
//     free(b1->lines);
//     b1->lines = NULL;
//     b1->count = b1->cap = 0;
// }
// static void badlist_add(BadList *b1, unsigned long long ln)
// {
//     if (b1->count == b1->cap)
//     {
//         size_t nc = b1->cap ? b1->cap * 2 : 64;
//         unsigned long long *p = realloc(b1->lines, nc * sizeof(*p));
//         if (!p)
//             return;
//         b1->lines = p;
//         b1->cap = nc;
//     }
//     b1->lines[b1->count++] = ln;
// }

// static void diee(const char *m)
// {
//     fprintf(stderr, "%s\n", m);
//     exit(1);
// }

// /* file SM3 -> hex */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//     {
//         fclose(f);
//         return -1;
//     }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         fclose(f);
//         return -1;
//     }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
//     {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(f);
//             return -1;
//         }
//     }
//     fclose(f);
//     unsigned char md[32];
//     unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     for (int i = 0; i < 32; i++)
//     {
//         out_hex[i * 2] = HEX_CHARS[(md[i] >> 4) & 0xF];
//         out_hex[i * 2 + 1] = HEX_CHARS[md[i] & 0xF];
//     }
//     out_hex[64] = 0;
//     return 0;
// }

// /* base64 decode helper */
// static unsigned char *b64_decode(const char *b64, size_t *out_len)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m)
//     {
//         if (b)
//             BIO_free(b);
//         if (m)
//             BIO_free(m);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0)
//     {
//         free(buf);
//         return NULL;
//     }
//     *out_len = (size_t)n;
//     return buf;
// }

// /* 读取 seal 文件（简单 key=value 解析） */
// struct seal
// {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };
// static char *trimws(char *s)
// {
//     while (*s == ' ' || *s == '\t')
//         s++;
//     size_t n = strlen(s);
//     while (n > 0 && (s[n - 1] == ' ' || s[n - 1] == '\t' || s[n - 1] == '\r' || s[n - 1] == '\n'))
//         s[--n] = 0;
//     return s;
// }
// static void trimnl(char *s)
// {
//     size_t n = strlen(s);
//     while (n && (s[n - 1] == '\n' || s[n - 1] == '\r'))
//         s[--n] = 0;
// }
// static int parse_seal(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1)
//     {
//         if (line[0] == '#' || line[0] == 0)
//             continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq)
//             continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq + 1);
//         fprintf(stderr, "[DEBUG] Parsing: '%s' = '%s'\n", k, v);
//         if (strcmp(k, "version") == 0)
//             S->version = strdup(v);
//         else if (strcmp(k, "hash") == 0)
//             S->hash = strdup(v);
//         else if (strcmp(k, "sign") == 0)
//             S->sign = strdup(v);
//         else if (strcmp(k, "sm2_id") == 0)
//             S->sm2_id = strdup(v);
//         else if (strcmp(k, "path") == 0)
//             S->path = strdup(v);
//         else if (strcmp(k, "records") == 0)
//             S->records = strdup(v);
//         else if (strcmp(k, "chain") == 0)
//             S->chain = strdup(v);
//         else if (strcmp(k, "key_fpr") == 0)
//             S->key_fpr = strdup(v);
//         else if (strcmp(k, "cert_der_base64") == 0)
//             S->cert_b64 = strdup(v);
//         else if (strcmp(k, "sig_base64") == 0)
//             S->sig_b64 = strdup(v);
//         else if (strcmp(k, "index_sm3") == 0)
//             S->index_sm3 = strdup(v);
//     }
//     free(line);
//     fclose(f);
//     return (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) ? 0 : -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version);
//     free(S->hash);
//     free(S->sign);
//     free(S->sm2_id);
//     free(S->path);
//     free(S->records);
//     free(S->chain);
//     free(S->key_fpr);
//     free(S->cert_b64);
//     free(S->sig_b64);
//     free(S->index_sm3);
// }

// /* 读取 idx 最后一条记录：返回 last_i, last_off, last_chash(hex) */
// static int read_idx_last(const char *idx_path, unsigned long long *out_i, unsigned long long *out_off, char out_chash_hex[65])
// {
//     FILE *f = fopen(idx_path, "r");
//     if (!f)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long last_i = 0, last_off = 0;
//     char last_chash[65] = {0};
//     while ((n = getline(&line, &cap, f)) != -1)
//     {
//         if (line[0] == '#' || line[0] == 0)
//             continue;
//         unsigned long long i = 0, off = 0;
//         char lhex[128] = {0}, chex[128] = {0};
//         if (sscanf(line, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4)
//         {
//             last_i = i;
//             last_off = off;
//             strncpy(last_chash, chex, sizeof(last_chash) - 1);
//         }
//     }
//     free(line);
//     fclose(f);
//     if (last_i == 0)
//         return -1;
//     strncpy(out_chash_hex, last_chash, 65);
//     *out_i = last_i;
//     *out_off = last_off;
//     return 0;
// }

// /* 计算文件行数（快速） */
// static unsigned long long count_file_lines(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//     {
//         ln++;
//     }
//     free(line);
//     fclose(f);
//     return ln;
// }

// /*
//  比较 audit.log 与 idx，**只比较前 max_lines**（如果 max_lines 为 0 则比较全部）。
//  返回：bad_count，出错返回 (unsigned long long)-1
//  输出被篡改行号到 out_bad（可为 NULL）
//  scanned_lines 返回实际扫描的行数（<= max_lines）
// */
// static unsigned long long compare_with_index_limit(const char *log_path, const char *idx_path,
//                                                   BadList *out_bad,
//                                                   unsigned long long max_lines,
//                                                   unsigned long long *scanned_lines)
// {
//     if (scanned_lines)
//         *scanned_lines = 0;
//     if (out_bad)
//         badlist_init(out_bad);
//     FILE *lf = fopen(log_path, "rb");
//     FILE *ix = fopen(idx_path, "r");
//     if (!lf || !ix)
//     {
//         if (lf)
//             fclose(lf);
//         if (ix)
//             fclose(ix);
//         return (unsigned long long)-1;
//     }

//     // 跳过 idx 头部直至 i=
//     char buf[1024];
//     long back_pos = 0;
//     while (fgets(buf, sizeof(buf), ix))
//     {
//         if (buf[0] == '#')
//             continue;
//         if (strncmp(buf, "i=", 2) == 0)
//         {
//             back_pos = ftell(ix) - (long)strlen(buf);
//             break;
//         }
//     }
//     if (back_pos > 0)
//         fseek(ix, back_pos, SEEK_SET);

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//     {
//         fclose(lf);
//         fclose(ix);
//         return (unsigned long long)-1;
//     }

//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     unsigned long long bad = 0;
//     static const char hex[] = "0123456789abcdef";

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         if (max_lines != 0 && ln > max_lines)
//             break;

//         if (!fgets(buf, sizeof(buf), ix))
//         {
//             bad++;
//             if (out_bad)
//                 badlist_add(out_bad, ln);
//             continue;
//         }

//         unsigned long long idx_ln = 0, off = 0;
//         char lhex[128], chex[128];
//         if (sscanf(buf, "i=%llu off=%llu lhash=%64s chash=%64s", &idx_ln, &off, lhex, chex) != 4 || idx_ln != ln)
//         {
//             bad++;
//             if (out_bad)
//                 badlist_add(out_bad, ln);
//             continue;
//         }

//         // 计算当前行的 lhash
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32];
//         unsigned int lm = 0;
//         int ok1 = c1 &&
//                   EVP_DigestInit_ex(c1, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(c1, line, (size_t)n) == 1 &&
//                   EVP_DigestFinal_ex(c1, L, &lm) == 1 && lm == 32;
//         if (!ok1)
//         {
//             if (c1)
//                 EVP_MD_CTX_free(c1);
//             fclose(lf);
//             fclose(ix);
//             free(line);
//             return (unsigned long long)-1;
//         }
//         EVP_MD_CTX_free(c1);

//         // 转 hex
//         char Lhex[65];
//         for (int i = 0; i < 32; i++)
//         {
//             Lhex[i * 2] = hex[(L[i] >> 4) & 0xF];
//             Lhex[i * 2 + 1] = hex[L[i] & 0xF];
//         }
//         Lhex[64] = 0;

//         if (strcmp(Lhex, lhex) != 0)
//         {
//             bad++;
//             if (out_bad)
//                 badlist_add(out_bad, ln);
//         }
//     }

//     free(line);
//     fclose(lf);
//     fclose(ix);
//     if (scanned_lines)
//         *scanned_lines = ((max_lines == 0) ? ln : (ln > max_lines ? max_lines : ln));
//     return bad;
// }

// /* 计算文件前 N 行的链（SM3）。如果 limit_lines==0 则计算全部行。返回0成功，out_lines返回实际行数被处理 */
// static int sm3_chain_file_limit(const char *path, unsigned char out32[32], unsigned long long *out_lines, unsigned long long limit_lines)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     unsigned char H[32] = {0};
//     unsigned long long lines = 0;

//     FILE *fp = fopen(path, "rb");
//     if (!fp)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     while ((n = getline(&line, &cap, fp)) != -1)
//     {
//         lines++;
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx)
//         {
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         if (EVP_DigestUpdate(ctx, line, (size_t)n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         unsigned int mdlen = 0;
//         if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);

//         if (limit_lines != 0 && lines >= limit_lines)
//             break;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     if (out_lines)
//         *out_lines = lines;
//     return 0;
// }

// /* 计算 cert 指纹（SM3 hex） */
// static int x509_sm3_fprint_hex(X509 *cert, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     unsigned char *der = NULL;
//     int derlen = i2d_X509(cert, &der);
//     if (derlen <= 0)
//         return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//     {
//         OPENSSL_free(der);
//         return -1;
//     }
//     unsigned char md[EVP_MAX_MD_SIZE];
//     unsigned int mdlen = 0;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der, derlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, md, &mdlen) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der);
//     for (unsigned i = 0; i < mdlen; i++)
//     {
//         out_hex[i * 2] = HEX_CHARS[(md[i] >> 4) & 0xF];
//         out_hex[i * 2 + 1] = HEX_CHARS[md[i] & 0xF];
//     }
//     out_hex[mdlen * 2] = 0;
//     return 0;
// }

// /* build canonical used for signature verification (same fields as seal) */
// static char *build_canonical(const struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//                  "version=%s\n"
//                  "hash=%s\n"
//                  "sign=%s\n"
//                  "sm2_id=%s\n"
//                  "path=%s\n"
//                  "records=%s\n"
//                  "chain=%s\n"
//                  "key_fpr=%s\n"
//                  "index_sm3=%s\n",
//                  S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0)
//         return NULL;
//     return buf;
// }

// /* print prefix hex for debug */
// static void dump_hex_prefix(const char *tag, const unsigned char *buf, size_t len)
// {
//     size_t n = len > 128 ? 128 : len;
//     fprintf(stderr, "[DBG] %s canon len=%zu hex:", tag, len);
//     for (size_t i = 0; i < n; i++)
//         fprintf(stderr, "%02x", buf[i]);
//     if (len > n)
//         fprintf(stderr, "...");
//     fputc('\n', stderr);
// }

// /* main */
// int main(int argc, char **argv)
// {
//     if (argc != 2)
//     {
//         fprintf(stderr, "Usage: %s /var/log/audit/audit.log.seal\n", argv[0]);
//         return 1;
//     }

//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();
//     if (gm_crypto_init() != 0)
//         diee("gm init failed");
//     if (!EVP_get_digestbyname("sm3"))
//         diee("[ERROR] 无 sm3");

//     struct seal S;
//     if (parse_seal(argv[1], &S) != 0)
//         diee("parse seal failed");

//     if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0)
//         diee("[ERROR] seal 非 sm2/sm3");
//     if (strcmp(S.sm2_id, SM2_ID) != 0)
//         diee("[ERROR] SM2_ID 不匹配");

//     /* 解析 seal 中的 records */
//     unsigned long long seal_records = 0;
//     if (S.records)
//         seal_records = strtoull(S.records, NULL, 10);

//     /* count audit.log 实际行数 */
//     unsigned long long file_lines = count_file_lines(S.path);
//     fprintf(stderr, "[DEBUG] audit.log has %llu lines; seal.records=%llu\n", file_lines, seal_records);

//     /* 如果文件行数小于 seal.records：说明日志被截断/缺失 */
//     if (file_lines < seal_records)
//     {
//         char bufmsg[256];
//         snprintf(bufmsg, sizeof(bufmsg), "Audit log truncated: seal expects %llu records but file has %llu lines", seal_records, file_lines);
//         trigger_alarm(bufmsg);
//         fprintf(stderr, "[FAIL] %s\n", bufmsg);

//         // 还是尝试用现有 idx 与已有行做对比，列出被篡改的行（在现存行范围内）
//         char *idx_path = NULL;
//         if (asprintf(&idx_path, "%s.idx", S.path) < 0)
//             idx_path = NULL;

//         if (idx_path)
//         {
//             // 逐行比对现有 file_lines（最多 file_lines）
//             BadList bl;
//             badlist_init(&bl);
//             unsigned long long scanned = 0;
//             unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, file_lines, &scanned);
//             if (bad == (unsigned long long)-1)
//             {
//                 fprintf(stderr, "[ERR] failed to compare with index\n");
//             }
//             else
//             {
//                 fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned, bad);
//                 if (bl.count > 0)
//                 {
//                     fprintf(stderr, "[DETAIL] bad line list: ");
//                     for (size_t i = 0; i < bl.count; i++)
//                     {
//                         fprintf(stderr, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                     }
//                     fputc('\n', stderr);
//                     char msg[4096];
//                     size_t pos = 0;
//                     pos += snprintf(msg + pos, sizeof(msg) - pos, "Tampered lines: ");
//                     for (size_t i = 0; i < bl.count && pos < sizeof(msg) - 32; i++)
//                     {
//                         pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                     }
//                     trigger_alarm(msg);
//                 }
//             }
//             badlist_free(&bl);
//             free(idx_path);
//         }
//         else
//         {
//             fprintf(stderr, "[WARN] idx path not available; cannot do fine-grained diff\n");
//         }

//         free_seal(&S);
//         gm_crypto_cleanup();
//         return 1;
//     }

//     /* 现在 file_lines >= seal_records：
//        只对前 seal_records 行计算链（如果 seal_records==0，则计算全部）
//     */
//     unsigned char chain[32];
//     unsigned long long scanned_lines = 0;
//     if (sm3_chain_file_limit(S.path, chain, &scanned_lines, seal_records == 0 ? 0 : seal_records) != 0)
//         diee("recompute chain failed");
//     char chain_hex[65];
//     for (int i = 0; i < 32; i++)
//     {
//         chain_hex[i * 2] = HEX_CHARS[(chain[i] >> 4) & 0xF];
//         chain_hex[i * 2 + 1] = HEX_CHARS[chain[i] & 0xF];
//     }
//     chain_hex[64] = 0;

//     if (strcmp(chain_hex, S.chain) != 0)
//     {
//         // 整体不一致
//         trigger_alarm("Chain mismatch detected: audit log may be tampered!");
//         fprintf(stderr, "[FAIL] chain mismatch (文件被改动?)\n");
//         fprintf(stderr, "[DEBUG] recomputed chain (first %llu lines) = %s\n", seal_records == 0 ? scanned_lines : seal_records, chain_hex);

//         // 如果 seal 中带了 index_sm3，则进一步校验 idx 并细粒度比对（对前 seal_records 行）
//         if (S.index_sm3 && *S.index_sm3)
//         {
//             char *idx_path = NULL;
//             if (asprintf(&idx_path, "%s.idx", S.path) < 0)
//                 idx_path = NULL;

//             if (idx_path)
//             {
//                 char idx_sm3_now[65];
//                 if (sm3_file_hex(idx_path, idx_sm3_now) == 0)
//                 {
//                     if (strcmp(idx_sm3_now, S.index_sm3) != 0)
//                     {
//                         trigger_alarm("Index file hash mismatch: index may also be tampered!");
//                         fprintf(stderr, "[ALERT] index_sm3 mismatch: %s (seal) vs %s (current)\n",
//                                 S.index_sm3, idx_sm3_now);
//                     }
//                     else
//                     {
//                         // 索引一致，逐行比对（仅前 seal_records 行）
//                         BadList bl;
//                         badlist_init(&bl);
//                         unsigned long long scanned = 0;
//                         unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, seal_records, &scanned);
//                         if (bad == (unsigned long long)-1)
//                         {
//                             fprintf(stderr, "[ERR] failed to compare with index\n");
//                         }
//                         else
//                         {
//                             fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned, bad);
//                             if (bl.count > 0)
//                             {
//                                 fprintf(stderr, "[DETAIL] bad line list: ");
//                                 for (size_t i = 0; i < bl.count; i++)
//                                 {
//                                     fprintf(stderr, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                                 }
//                                 fputc('\n', stderr);

//                                 // 触发报警（含坏行列表）
//                                 char msg[4096];
//                                 size_t pos = 0;
//                                 pos += snprintf(msg + pos, sizeof(msg) - pos, "Tampered lines: ");
//                                 for (size_t i = 0; i < bl.count && pos < sizeof(msg) - 32; i++)
//                                 {
//                                     pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                                 }
//                                 trigger_alarm(msg);
//                             }
//                             else
//                             {
//                                 fprintf(stderr, "[DETAIL] no mismatched lhash in first %llu lines compared to current idx\n", seal_records);
//                             }
//                         }
//                         badlist_free(&bl);
//                     }
//                 }
//                 else
//                 {
//                     fprintf(stderr, "[WARN] cannot compute sm3 of %s\n", idx_path);
//                 }
//                 free(idx_path);
//             }
//         }

//         free_seal(&S);
//         gm_crypto_cleanup();
//         return 1;
//     }

//     /* chain match：继续证书指纹和签名验证 */
//     size_t cert_len = 0;
//     unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der)
//         diee("cert b64 decode failed");
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert)
//         diee("d2i_X509 failed");
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub)
//         diee("X509_get_pubkey failed");
//     /* 必须是 SM2 曲线 */
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey)
//     {
//         trigger_alarm("Public key is not EC key!");
//         diee("pub is not EC");
//     }
//     const EC_GROUP *grp = EC_KEY_get0_group(eckey);
//     if (!grp || EC_GROUP_get_curve_name(grp) != NID_sm2)
//     {
//         trigger_alarm("Certificate is not SM2 type!");
//         diee("[ERROR] 证书不是SM2");
//     }

//     /* cert fingerprint SM3 */
//     unsigned char *der2 = NULL;
//     int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0)
//         diee("i2d_X509 failed");
//     unsigned char md[EVP_MAX_MD_SIZE];
//     unsigned int mdlen = 0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//         diee("ctx");
//     if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, md, &mdlen) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der2);
//         diee("fingerprint digest failed");
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der2);
//     char fpr_hex[65];
//     for (unsigned i = 0; i < mdlen; i++)
//     {
//         fpr_hex[i * 2] = HEX_CHARS[(md[i] >> 4) & 0xF];
//         fpr_hex[i * 2 + 1] = HEX_CHARS[md[i] & 0xF];
//     }
//     fpr_hex[mdlen * 2] = 0;
//     if (strcmp(fpr_hex, S.key_fpr) != 0)
//     {
//         trigger_alarm("Certificate fingerprint mismatch detected!");
//         diee("[FAIL] key_fpr mismatch");
//     }

//     /* 构造 canon 并验签 */
//     char *canon = build_canonical(&S);
//     if (!canon)
//         diee("asprintf");

//     dump_hex_prefix("verify", (const unsigned char *)canon, strlen(canon));

//     size_t siglen = 0;
//     unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig)
//         diee("sig b64 decode failed");

//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon),
//                           SM2_ID, sig, siglen);
//     free(sig);
//     free(canon);
//     EVP_PKEY_free(pub);
//     X509_free(cert);
//     gm_crypto_cleanup();

//     if (v == 1)
//     {
//         fprintf(stderr, "[OK] verify success\n");
//     }
//     else if (v == 0)
//     {
//         trigger_alarm("Signature mismatch detected!");
//         diee("[FAIL] signature invalid");
//     }
//     else
//     {
//         trigger_alarm("Signature verification error!");
//         diee("[FAIL] verify error");
//     }

//     free_seal(&S);
//     return 0;
// }

//----------------解决.isx  .seal和audit.log行数不一致问题 1-------
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <string.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <sys/stat.h>
// #include <limits.h>

// #include <openssl/evp.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/err.h>

// #include "alarm.c"      // 你已有的 trigger_alarm 实现
// #include "../../lib/gm_crypto.h" // gm_sm2_verify

// #define SM2_ID "1234567812345678"
// static const char HEX_CHARS[] = "0123456789abcdef";

// /* ----------------- 小工具 ----------------- */

// static void diee(const char *m)
// {
//     fprintf(stderr, "%s\n", m);
//     exit(1);
// }

// static void hexdump_buf(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     if (outlen < len*2 + 1) return;
//     for (size_t i = 0; i < len; i++) {
//         out[i*2]     = HEX_CHARS[(buf[i] >> 4) & 0xF];
//         out[i*2 + 1] = HEX_CHARS[buf[i] & 0xF];
//     }
//     out[len*2] = 0;
// }

// /* base64 decode (no NL handling) */
// static unsigned char *b64_decode(const char *b64, size_t *out_len)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *out_len = (size_t)n;
//     return buf;
// }

// /* 计算文件 SM3 -> hex (整文件) */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump_buf(md, 32, out_hex, 65);
//     return 0;
// }

// /* 统计文件行数 */
// static int count_lines(const char *path, unsigned long long *out_lines)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1) ln++;
//     free(line);
//     fclose(f);
//     *out_lines = ln;
//     return 0;
// }

// /* 计算文件前 N 行的 chain（SM3），同时输出实际扫描行数（<= N） */
// static int sm3_chain_file_limit(const char *path, unsigned long long limit_lines, unsigned char out32[32], unsigned long long *out_scanned)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;

//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1) {
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, line, (size_t)n) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         unsigned int mdlen = 0;
//         if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     if (out_scanned) *out_scanned = scanned;
//     return 0;
// }

// /* 计算整个文件 chain（SM3）并返回行数（封装便捷） */
// static int sm3_chain_file_whole(const char *path, unsigned char out32[32], unsigned long long *out_lines)
// {
//     return sm3_chain_file_limit(path, (unsigned long long)-1, out32, out_lines);
// }

// /* ----------------- Seal 解析 ----------------- */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// static char *trimws(char *s) {
//     while (*s == ' ' || *s == '\t') s++;
//     size_t n = strlen(s);
//     while (n > 0 && (s[n-1] == ' ' || s[n-1] == '\t' || s[n-1] == '\r' || s[n-1] == '\n')) s[--n]=0;
//     return s;
// }
// static void trimnl(char *s) {
//     size_t n = strlen(s);
//     while (n && (s[n-1]=='\n' || s[n-1]=='\r')) s[--n] = 0;
// }

// static int parse_seal(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq+1);
//         fprintf(stderr, "[DEBUG] Parsing: '%s' = '%s'\n", k, v);
//         if (strcmp(k,"version")==0) S->version = strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash = strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign = strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id = strdup(v);
//         else if (strcmp(k,"path")==0) S->path = strdup(v);
//         else if (strcmp(k,"records")==0) S->records = strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain = strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr = strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64 = strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64 = strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3 = strdup(v);
//     }
//     free(line);
//     fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
//         return 0;
//     return -1;
// }

// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }

// /* 构造 canonical 用于验签（与 seal/producer 保持一致） */
// static char *build_canonical(const struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//         "version=%s\n"
//         "hash=%s\n"
//         "sign=%s\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%s\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
//     return buf;
// }

// static void dump_hex_prefix(const char *tag, const unsigned char *buf, size_t len)
// {
//     size_t n = len > 128 ? 128 : len;
//     fprintf(stderr, "[DBG] %s canon len=%zu hex:", tag, len);
//     for (size_t i=0;i<n;i++) fprintf(stderr,"%02x",buf[i]);
//     if (len>n) fprintf(stderr,"...");
//     fputc('\n', stderr);
// }

// /* ----------------- 索引比较（带行限制） -----------------
//    compare_with_index_limit: 只比较前 max_lines 行（如果 max_lines==0 则比较全部）。
//    out_bad 为 NULL 则不收集列表；scanned_lines 输出实际扫描到的行数。
//    返回坏行数，出错返回 (unsigned long long)-1
// */
// typedef struct {
//     unsigned long long *lines;
//     size_t count, cap;
// } BadList;
// static void badlist_init(BadList *b) { b->lines = NULL; b->count = b->cap = 0; }
// static void badlist_free(BadList *b) { free(b->lines); b->lines = NULL; b->count = b->cap = 0; }
// static void badlist_add(BadList *b, unsigned long long ln) {
//     if (!b) return;
//     if (b->count == b->cap) {
//         size_t nc = b->cap ? b->cap * 2 : 64;
//         unsigned long long *p = realloc(b->lines, nc * sizeof(*p));
//         if (!p) return;
//         b->lines = p; b->cap = nc;
//     }
//     b->lines[b->count++] = ln;
// }

// static unsigned long long compare_with_index_limit(const char *log_path, const char *idx_path,
//                                                    BadList *out_bad,
//                                                    unsigned long long max_lines,
//                                                    unsigned long long *scanned_lines)
// {
//     if (scanned_lines) *scanned_lines = 0;
//     if (out_bad) badlist_init(out_bad);
//     FILE *lf = fopen(log_path,"rb");
//     FILE *ix = fopen(idx_path,"r");
//     if (!lf || !ix) { if (lf) fclose(lf); if (ix) fclose(ix); return (unsigned long long)-1; }

//     /* 跳过 idx 头，直到 i= */
//     char ibuf[1024];
//     long back_pos = 0;
//     while (fgets(ibuf, sizeof(ibuf), ix)) {
//         if (ibuf[0] == '#') continue;
//         if (strncmp(ibuf, "i=", 2) == 0) {
//             back_pos = ftell(ix) - (long)strlen(ibuf);
//             break;
//         }
//     }
//     if (back_pos > 0) fseek(ix, back_pos, SEEK_SET);

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { fclose(lf); fclose(ix); return (unsigned long long)-1; }

//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, bad = 0;
//     static const char hex[] = "0123456789abcdef";

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         if (max_lines != 0 && ln > max_lines) break;

//         if (!fgets(ibuf, sizeof(ibuf), ix)) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//             continue;
//         }
//         unsigned long long idx_ln=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(ibuf, "i=%llu off=%llu lhash=%64s chash=%64s", &idx_ln, &off, lhex, chex) != 4 || idx_ln != ln) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//             continue;
//         }

//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         int ok1 = ctx &&
//                   EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, line, (size_t)n) == 1 &&
//                   EVP_DigestFinal_ex(ctx, L, &lm) == 1 && lm == 32;
//         if (!ok1) {
//             if (ctx) EVP_MD_CTX_free(ctx);
//             fclose(lf); fclose(ix); free(line);
//             return (unsigned long long)-1;
//         }
//         EVP_MD_CTX_free(ctx);
//         char Lhex[65];
//         for (int i=0;i<32;i++) { Lhex[i*2] = hex[(L[i] >> 4) & 0xF]; Lhex[i*2+1] = hex[L[i] & 0xF]; }
//         Lhex[64]=0;
//         if (strcmp(Lhex, lhex) != 0) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//         }
//     }

//     free(line);
//     fclose(lf);
//     fclose(ix);
//     if (scanned_lines) *scanned_lines = (max_lines == 0) ? ln : (ln > max_lines ? max_lines : ln);
//     return bad;
// }

// /* ----------------- main ----------------- */

// int main(int argc, char **argv)
// {
//     if (argc != 2) {
//         fprintf(stderr, "Usage: %s /var/log/audit/audit.log.seal\n", argv[0]);
//         return 1;
//     }

//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();
//     if (gm_crypto_init() != 0) diee("gm init failed");
//     if (!EVP_get_digestbyname("sm3")) diee("[ERROR] 无 sm3");

//     struct seal S;
//     if (parse_seal(argv[1], &S) != 0) diee("parse seal failed");

//     if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0) diee("[ERROR] seal 非 sm2/sm3");
//     if (strcmp(S.sm2_id, SM2_ID) != 0) diee("[ERROR] SM2_ID 不匹配");

//     /* 解析 seal 中的 records */
//     unsigned long long seal_records = 0;
//     if (S.records) seal_records = strtoull(S.records, NULL, 10);

//     /* 读取 audit.log 实际行数 */
//     unsigned long long log_lines = 0;
//     if (count_lines(S.path, &log_lines) != 0) {
//         trigger_alarm("Cannot open audit log");
//         diee("[ERROR] cannot count lines of audit.log");
//     }
//     fprintf(stderr, "[DEBUG] audit.log has %llu lines; seal.records=%llu\n", log_lines, seal_records);

//     /* idx path */
//     char *idx_path = NULL;
//     if (asprintf(&idx_path, "%s.idx", S.path) < 0) idx_path = NULL;

//     /* 情况分支：
//        1) log_lines < seal_records -> 严重：日志缺失/截断（报警）
//        2) log_lines >= seal_records -> 正常（可能有滞后）：仅验证前 seal_records 行（seal 覆盖的范围）
//     */

//     if (log_lines < seal_records) {
//         /* 截断：报警并尽力对比现有行 */
//         trigger_alarm("Audit log truncated: seal expects more records than present");
//         fprintf(stderr, "[ALARM] Audit log truncated: seal expects %llu records but file has %llu lines\n",
//                 seal_records, log_lines);

//         if (idx_path) {
//             BadList bl; badlist_init(&bl);
//             unsigned long long scanned = 0;
//             unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, log_lines, &scanned);
//             if (bad == (unsigned long long)-1) {
//                 fprintf(stderr, "[ERR] failed to compare with index\n");
//             } else {
//                 fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned, bad);
//                 if (bl.count > 0) {
//                     fprintf(stderr, "[DETAIL] bad line list: ");
//                     for (size_t i=0;i<bl.count;i++) {
//                         fprintf(stderr, "%llu%s", bl.lines[i], (i+1 < bl.count) ? "," : "");
//                     }
//                     fputc('\n', stderr);
//                     /* 报警列出被篡改行 */
//                     char msg[4096]; size_t pos=0;
//                     pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
//                     for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                     trigger_alarm(msg);
//                 }
//             }
//             badlist_free(&bl);
//         } else {
//             fprintf(stderr, "[WARN] idx path not available\n");
//         }
//         free_seal(&S);
//         if (idx_path) free(idx_path);
//         gm_crypto_cleanup();
//         return 1;
//     }

//     /* 至此 log_lines >= seal_records，验证前 seal_records 行（seal 覆盖的范围）*/
//     unsigned long long to_check = seal_records;
//     if (to_check == 0) {
//         /* 若 seal.records==0，仍可认为是空链：只验签证书/签名即可 */
//         to_check = 0;
//     }

//     /* 先计算前 to_check 行的 chain，并与 seal.chain 比较 */
//     unsigned char now_chain_bin[32]; unsigned long long scanned = 0;
//     if (to_check > 0) {
//         if (sm3_chain_file_limit(S.path, to_check, now_chain_bin, &scanned) != 0) {
//             fprintf(stderr, "[ERR] failed to compute chain over first %llu lines\n", to_check);
//             free_seal(&S); if (idx_path) free(idx_path); gm_crypto_cleanup(); return 1;
//         }
//     } else {
//         /* to_check==0 -> chain should be SM3 of empty chain (starting with H=0, no lines) => H stays zero */
//         memset(now_chain_bin, 0, 32);
//         scanned = 0;
//     }
//     char now_chain_hex[65]; hexdump_buf(now_chain_bin, 32, now_chain_hex, sizeof(now_chain_hex));

//     if (strcmp(now_chain_hex, S.chain) != 0) {
//         /* chain mismatch -> 报警；但这不一定表示 audit.log 的尾部被篡改（因为我们只比较前 seal_records 行）
//            可能由 idx 被修改或seal与idx之间不一致造成。我们尝试用 idx 做逐行对比以定位被篡改的行（只对前 to_check 行）。 */
//         trigger_alarm("Chain mismatch detected: audit log may be tampered!");
//         fprintf(stderr, "[FAIL] chain mismatch (文件被改动?)\n");
//         fprintf(stderr, "[DEBUG] recomputed chain (first %llu lines) = %s\n", to_check, now_chain_hex);

//         if (S.index_sm3 && *S.index_sm3 && idx_path) {
//             char idx_sm3_now[65];
//             if (sm3_file_hex(idx_path, idx_sm3_now) == 0) {
//                 if (strcmp(idx_sm3_now, S.index_sm3) != 0) {
//                     trigger_alarm("Index file hash mismatch: index may also be tampered!");
//                     fprintf(stderr, "[ALERT] index_sm3 mismatch: %s (seal) vs %s (current)\n", S.index_sm3, idx_sm3_now);
//                 } else {
//                     /* idx 与 seal.index_sm3 匹配：用 idx 逐行比对，找出具体坏行（只对前 to_check 行） */
//                     BadList bl; badlist_init(&bl);
//                     unsigned long long scanned2 = 0;
//                     unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, to_check, &scanned2);
//                     if (bad == (unsigned long long)-1) {
//                         fprintf(stderr, "[ERR] failed to compare with index\n");
//                     } else {
//                         fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned2, bad);
//                         if (bl.count > 0) {
//                             fprintf(stderr, "[DETAIL] bad line list: ");
//                             for (size_t i=0;i<bl.count;i++) {
//                                 fprintf(stderr, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                             }
//                             fputc('\n', stderr);
//                             char msg[4096]; size_t pos=0;
//                             pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
//                             for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                             trigger_alarm(msg);
//                         } else {
//                             fprintf(stderr, "[DETAIL] No mismatched lhash in first %llu lines compared to current idx\n", to_check);
//                         }
//                     }
//                     badlist_free(&bl);
//                 }
//             } else {
//                 fprintf(stderr, "[WARN] cannot compute sm3 of %s\n", idx_path);
//             }
//         } else {
//             fprintf(stderr, "[WARN] index_sm3 or idx_path not available; cannot do fine-grained diff\n");
//         }

//         free_seal(&S); if (idx_path) free(idx_path); gm_crypto_cleanup();
//         return 1;
//     }

//     /* 到这里：前 to_check 行 chain 匹配 seal.chain（或者 to_check==0 且一致） -> 再验签证书指纹与签名 */
//     if (idx_path) {
//         char idx_sm3_now[65];
//         if (sm3_file_hex(idx_path, idx_sm3_now) == 0) {
//             if (strcmp(idx_sm3_now, S.index_sm3) != 0) {
//                 trigger_alarm("Index file hash mismatch: index may be tampered!");
//                 fprintf(stderr, "[ALERT] index_sm3 mismatch: %s (seal) vs %s (current)\n", S.index_sm3, idx_sm3_now);
//                 /* 但不直接退出——仍尝试验签（取决于策略，你可以选择直接失败）*/
//             } else {
//                 fprintf(stderr, "[DEBUG] index_sm3 matches\n");
//             }
//         } else {
//             fprintf(stderr, "[WARN] cannot compute sm3 of %s\n", idx_path);
//         }
//     }

//     /* 取证书公钥并核对指纹 */
//     size_t cert_len = 0;
//     unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) diee("cert b64 decode failed");
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) diee("d2i_X509 failed");
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) diee("X509_get_pubkey failed");
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey) { trigger_alarm("Public key is not EC key!"); diee("pub is not EC"); }
//     const EC_GROUP *grp = EC_KEY_get0_group(eckey);
//     if (!grp || EC_GROUP_get_curve_name(grp) != NID_sm2) { trigger_alarm("Certificate is not SM2 type!"); diee("[ERROR] 证书不是SM2"); }

//     /* 计算 cert 指纹 */
//     unsigned char *der2 = NULL;
//     int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0) diee("i2d_X509 failed");
//     unsigned char md[EVP_MAX_MD_SIZE]; unsigned int mdlen = 0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) diee("ctx");
//     if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, md, &mdlen) != 1) {
//         EVP_MD_CTX_free(ctx); OPENSSL_free(der2); diee("fingerprint digest failed");
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump_buf(md, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         trigger_alarm("Certificate fingerprint mismatch detected!");
//         diee("[FAIL] key_fpr mismatch");
//     }

//     /* 构造 canonical 并验签 */
//     char *canon = build_canonical(&S);
//     if (!canon) diee("asprintf");
//     dump_hex_prefix("verify", (const unsigned char *)canon, strlen(canon));
//     size_t siglen = 0;
//     unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) diee("sig b64 decode failed");

//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig);
//     free(canon);
//     EVP_PKEY_free(pub);
//     X509_free(cert);
//     gm_crypto_cleanup();

//     if (v == 1) {
//         fprintf(stderr, "[OK] verify success\n");
//         if (log_lines > seal_records) {
//             fprintf(stderr, "[WARN] audit.log has %llu lines but seal covers only %llu records; last %llu lines are not covered by this seal\n",
//                     log_lines, seal_records, log_lines - seal_records);
//         }
//         free_seal(&S);
//         if (idx_path) free(idx_path);
//         return 0;
//     } else if (v == 0) {
//         trigger_alarm("Signature mismatch detected!");
//         diee("[FAIL] signature invalid");
//     } else {
//         trigger_alarm("Signature verification error!");
//         diee("[FAIL] verify error");
//     }

//     /* 不会到这里 */
//     free_seal(&S);
//     if (idx_path) free(idx_path);
//     return 0;
// }

//////////////----解决.isx  .seal和audit.log行数不一致问题 版本2
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <string.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <sys/stat.h>
// #include <limits.h>

// #include <openssl/evp.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/err.h>

// #include "alarm.c"               // 你已有的 trigger_alarm 实现
// #include "../../lib/gm_crypto.h" // gm_sm2_verify

// #define SM2_ID "1234567812345678"
// static const char HEX_CHARS[] = "0123456789abcdef";

// /* ----------------- 小工具 ----------------- */

// static void diee(const char *m)
// {
//     fprintf(stderr, "%s\n", m);
//     exit(1);
// }

// static void hexdump_buf(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = HEX_CHARS[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = HEX_CHARS[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// /* base64 decode (no NL handling) */
// static unsigned char *b64_decode(const char *b64, size_t *out_len)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m)
//     {
//         if (b)
//             BIO_free(b);
//         if (m)
//             BIO_free(m);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     if (!buf)
//     {
//         BIO_free_all(b);
//         return NULL;
//     }
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0)
//     {
//         free(buf);
//         return NULL;
//     }
//     *out_len = (size_t)n;
//     return buf;
// }

// /* 计算文件 SM3 -> hex (整文件) */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//     {
//         fclose(f);
//         return -1;
//     }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         fclose(f);
//         return -1;
//     }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
//     {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(f);
//             return -1;
//         }
//     }
//     fclose(f);
//     unsigned char md[32];
//     unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     hexdump_buf(md, 32, out_hex, 65);
//     return 0;
// }

// /* 统计文件行数 */
// static int count_lines(const char *path, unsigned long long *out_lines)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     *out_lines = ln;
//     return 0;
// }

// /* 计算文件前 N 行的 chain（SM3），同时输出实际扫描行数（<= N） */
// static int sm3_chain_file_limit(const char *path, unsigned long long limit_lines, unsigned char out32[32], unsigned long long *out_scanned)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;

//     FILE *fp = fopen(path, "rb");
//     if (!fp)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1)
//     {
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx)
//         {
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         if (EVP_DigestUpdate(ctx, line, (size_t)n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         unsigned int mdlen = 0;
//         if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(fp);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     if (out_scanned)
//         *out_scanned = scanned;
//     return 0;
// }

// /* 计算整个文件 chain（SM3）并返回行数（封装便捷） */
// static int sm3_chain_file_whole(const char *path, unsigned char out32[32], unsigned long long *out_lines)
// {
//     return sm3_chain_file_limit(path, (unsigned long long)-1, out32, out_lines);
// }

// /* ----------------- Seal 解析 ----------------- */
// struct seal
// {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// static char *trimws(char *s)
// {
//     while (*s == ' ' || *s == '\t')
//         s++;
//     size_t n = strlen(s);
//     while (n > 0 && (s[n - 1] == ' ' || s[n - 1] == '\t' || s[n - 1] == '\r' || s[n - 1] == '\n'))
//         s[--n] = 0;
//     return s;
// }
// static void trimnl(char *s)
// {
//     size_t n = strlen(s);
//     while (n && (s[n - 1] == '\n' || s[n - 1] == '\r'))
//         s[--n] = 0;
// }

// static int parse_seal(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1)
//     {
//         if (line[0] == '#' || line[0] == 0)
//             continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq)
//             continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq + 1);
//         fprintf(stderr, "[DEBUG] Parsing: '%s' = '%s'\n", k, v);
//         if (strcmp(k, "version") == 0)
//             S->version = strdup(v);
//         else if (strcmp(k, "hash") == 0)
//             S->hash = strdup(v);
//         else if (strcmp(k, "sign") == 0)
//             S->sign = strdup(v);
//         else if (strcmp(k, "sm2_id") == 0)
//             S->sm2_id = strdup(v);
//         else if (strcmp(k, "path") == 0)
//             S->path = strdup(v);
//         else if (strcmp(k, "records") == 0)
//             S->records = strdup(v);
//         else if (strcmp(k, "chain") == 0)
//             S->chain = strdup(v);
//         else if (strcmp(k, "key_fpr") == 0)
//             S->key_fpr = strdup(v);
//         else if (strcmp(k, "cert_der_base64") == 0)
//             S->cert_b64 = strdup(v);
//         else if (strcmp(k, "sig_base64") == 0)
//             S->sig_b64 = strdup(v);
//         else if (strcmp(k, "index_sm3") == 0)
//             S->index_sm3 = strdup(v);
//     }
//     free(line);
//     fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
//         return 0;
//     return -1;
// }

// static void free_seal(struct seal *S)
// {
//     if (!S)
//         return;
//     free(S->version);
//     free(S->hash);
//     free(S->sign);
//     free(S->sm2_id);
//     free(S->path);
//     free(S->records);
//     free(S->chain);
//     free(S->key_fpr);
//     free(S->index_sm3);
//     free(S->cert_b64);
//     free(S->sig_b64);
// }

// /* 构造 canonical 用于验签（与 seal/producer 保持一致） */
// static char *build_canonical(const struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//                  "version=%s\n"
//                  "hash=%s\n"
//                  "sign=%s\n"
//                  "sm2_id=%s\n"
//                  "path=%s\n"
//                  "records=%s\n"
//                  "chain=%s\n"
//                  "key_fpr=%s\n"
//                  "index_sm3=%s\n",
//                  S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0)
//         return NULL;
//     return buf;
// }

// static void dump_hex_prefix(const char *tag, const unsigned char *buf, size_t len)
// {
//     size_t n = len > 128 ? 128 : len;
//     fprintf(stderr, "[DBG] %s canon len=%zu hex:", tag, len);
//     for (size_t i = 0; i < n; i++)
//         fprintf(stderr, "%02x", buf[i]);
//     if (len > n)
//         fprintf(stderr, "...");
//     fputc('\n', stderr);
// }

// /* ----------------- 索引比较（带行限制） -----------------
//    compare_with_index_limit: 只比较前 max_lines 行（如果 max_lines==0 则比较全部）。
//    out_bad 为 NULL 则不收集列表；scanned_lines 输出实际扫描到的行数。
//    返回坏行数，出错返回 (unsigned long long)-1
// */
// typedef struct
// {
//     unsigned long long *lines;
//     size_t count, cap;
// } BadList;
// static void badlist_init(BadList *b)
// {
//     if (!b)
//         return;
//     b->lines = NULL;
//     b->count = b->cap = 0;
// }
// static void badlist_free(BadList *b)
// {
//     if (!b)
//         return;
//     free(b->lines);
//     b->lines = NULL;
//     b->count = b->cap = 0;
// }
// static void badlist_add(BadList *b, unsigned long long ln)
// {
//     if (!b)
//         return;
//     if (b->count == b->cap)
//     {
//         size_t nc = b->cap ? b->cap * 2 : 64;
//         unsigned long long *p = realloc(b->lines, nc * sizeof(*p));
//         if (!p)
//             return;
//         b->lines = p;
//         b->cap = nc;
//     }
//     b->lines[b->count++] = ln;
// }

// static unsigned long long compare_with_index_limit(const char *log_path, const char *idx_path,
//                                                    BadList *out_bad,
//                                                    unsigned long long max_lines,
//                                                    unsigned long long *scanned_lines)
// {
//     if (scanned_lines)
//         *scanned_lines = 0;
//     if (out_bad)
//         badlist_init(out_bad);
//     FILE *lf = fopen(log_path, "rb");
//     FILE *ix = fopen(idx_path, "r");
//     if (!lf || !ix)
//     {
//         if (lf)
//             fclose(lf);
//         if (ix)
//             fclose(ix);
//         return (unsigned long long)-1;
//     }

//     /* 跳过 idx 头，直到 i= */
//     char ibuf[1024];
//     long back_pos = 0;
//     while (fgets(ibuf, sizeof(ibuf), ix))
//     {
//         if (ibuf[0] == '#')
//             continue;
//         if (strncmp(ibuf, "i=", 2) == 0)
//         {
//             back_pos = ftell(ix) - (long)strlen(ibuf);
//             break;
//         }
//     }
//     if (back_pos > 0)
//         fseek(ix, back_pos, SEEK_SET);

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//     {
//         fclose(lf);
//         fclose(ix);
//         return (unsigned long long)-1;
//     }

//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0, bad = 0;
//     static const char hex[] = "0123456789abcdef";

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         if (max_lines != 0 && ln > max_lines)
//             break;

//         if (!fgets(ibuf, sizeof(ibuf), ix))
//         {
//             bad++;
//             if (out_bad)
//                 badlist_add(out_bad, ln);
//             continue;
//         }
//         unsigned long long idx_ln = 0, off = 0;
//         char lhex[128] = {0}, chex[128] = {0};
//         if (sscanf(ibuf, "i=%llu off=%llu lhash=%64s chash=%64s", &idx_ln, &off, lhex, chex) != 4 || idx_ln != ln)
//         {
//             bad++;
//             if (out_bad)
//                 badlist_add(out_bad, ln);
//             continue;
//         }

//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char L[32];
//         unsigned int lm = 0;
//         int ok1 = ctx &&
//                   EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, line, (size_t)n) == 1 &&
//                   EVP_DigestFinal_ex(ctx, L, &lm) == 1 && lm == 32;
//         if (!ok1)
//         {
//             if (ctx)
//                 EVP_MD_CTX_free(ctx);
//             fclose(lf);
//             fclose(ix);
//             free(line);
//             return (unsigned long long)-1;
//         }
//         EVP_MD_CTX_free(ctx);
//         char Lhex[65];
//         for (int i = 0; i < 32; i++)
//         {
//             Lhex[i * 2] = hex[(L[i] >> 4) & 0xF];
//             Lhex[i * 2 + 1] = hex[L[i] & 0xF];
//         }
//         Lhex[64] = 0;
//         if (strcmp(Lhex, lhex) != 0)
//         {
//             bad++;
//             if (out_bad)
//                 badlist_add(out_bad, ln);
//         }
//     }

//     free(line);
//     fclose(lf);
//     fclose(ix);
//     if (scanned_lines)
//         *scanned_lines = (max_lines == 0) ? ln : (ln > max_lines ? max_lines : ln);
//     return bad;
// }

// /* ----------------- main ----------------- */

// int main(int argc, char **argv)
// {
//     if (argc != 2)
//     {
//         fprintf(stderr, "Usage: %s /var/log/audit/audit.log.seal\n", argv[0]);
//         return 1;
//     }

//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();
//     if (gm_crypto_init() != 0)
//         diee("gm init failed");
//     if (!EVP_get_digestbyname("sm3"))
//         diee("[ERROR] 无 sm3 support in OpenSSL");

//     struct seal S;
//     if (parse_seal(argv[1], &S) != 0)
//         diee("parse seal failed");

//     if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0)
//         diee("[ERROR] seal 非 sm2/sm3");
//     if (strcmp(S.sm2_id, SM2_ID) != 0)
//         diee("[ERROR] SM2_ID 不匹配");

//     /* 解析 seal 中的 records */
//     unsigned long long seal_records = 0;
//     if (S.records)
//         seal_records = strtoull(S.records, NULL, 10);

//     /* 读取 audit.log 实际行数 */
//     unsigned long long log_lines = 0;
//     if (count_lines(S.path, &log_lines) != 0)
//     {
//         trigger_alarm("Cannot open audit log");
//         diee("[ERROR] cannot count lines of audit.log");
//     }
//     fprintf(stderr, "[DEBUG] audit.log has %llu lines; seal.records=%llu\n", log_lines, seal_records);

//     /* idx path */
//     char *idx_path = NULL;
//     if (asprintf(&idx_path, "%s.idx", S.path) < 0)
//         idx_path = NULL;

//     /* 情况分支：截断 vs 正常 */
//     if (log_lines < seal_records)
//     {
//         /* 严重：日志缺失/截断（报警）*/
//         trigger_alarm("Audit log truncated: seal expects more records than present");
//         fprintf(stderr, "[ALARM] Audit log truncated: seal expects %llu records but file has %llu lines\n",
//                 seal_records, log_lines);

//         if (idx_path)
//         {
//             BadList bl;
//             badlist_init(&bl);
//             unsigned long long scanned = 0;
//             unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, log_lines, &scanned);
//             if (bad == (unsigned long long)-1)
//             {
//                 fprintf(stderr, "[ERR] failed to compare with index\n");
//             }
//             else
//             {
//                 fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned, bad);
//                 if (bl.count > 0)
//                 {
//                     fprintf(stderr, "[DETAIL] bad line list: ");
//                     for (size_t i = 0; i < bl.count; i++)
//                     {
//                         fprintf(stderr, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                     }
//                     fputc('\n', stderr);
//                     /* 报警列出被篡改行 */
//                     char msg[4096];
//                     size_t pos = 0;
//                     pos += snprintf(msg + pos, sizeof(msg) - pos, "Tampered lines: ");
//                     for (size_t i = 0; i < bl.count && pos < sizeof(msg) - 32; i++)
//                         pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                     trigger_alarm(msg);
//                 }
//             }
//             badlist_free(&bl);
//         }
//         else
//         {
//             fprintf(stderr, "[WARN] idx path not available\n");
//         }
//         free_seal(&S);
//         if (idx_path)
//             free(idx_path);
//         gm_crypto_cleanup();
//         return 1;
//     }

//     /* log_lines >= seal_records: 验证前 seal_records 行 */
//     unsigned long long to_check = seal_records;
//     if (to_check > 0)
//     {
//         unsigned char now_chain_bin[32];
//         unsigned long long scanned = 0;
//         if (sm3_chain_file_limit(S.path, to_check, now_chain_bin, &scanned) != 0)
//         {
//             fprintf(stderr, "[ERR] failed to compute chain over first %llu lines\n", to_check);
//             free_seal(&S);
//             if (idx_path)
//                 free(idx_path);
//             gm_crypto_cleanup();
//             return 1;
//         }
//         char now_chain_hex[65];
//         hexdump_buf(now_chain_bin, 32, now_chain_hex, sizeof(now_chain_hex));
//         if (strcmp(now_chain_hex, S.chain) != 0)
//         {
//             trigger_alarm("Chain mismatch detected: audit log may be tampered!");
//             fprintf(stderr, "[FAIL] chain mismatch (文件被改动?)\n");
//             fprintf(stderr, "[DEBUG] recomputed chain (first %llu lines) = %s\n", to_check, now_chain_hex);

//             if (S.index_sm3 && *S.index_sm3 && idx_path)
//             {
//                 char idx_sm3_now[65];
//                 if (sm3_file_hex(idx_path, idx_sm3_now) == 0)
//                 {
//                     if (strcmp(idx_sm3_now, S.index_sm3) != 0)
//                     {
//                         trigger_alarm("Index file hash mismatch: index may also be tampered!");
//                         fprintf(stderr, "[ALERT] index_sm3 mismatch: %s (seal) vs %s (current)\n", S.index_sm3, idx_sm3_now);
//                     }
//                     else
//                     {
//                         /* idx 与 seal.index_sm3 匹配：用 idx 逐行比对，找出具体坏行（只对前 to_check 行） */
//                         BadList bl;
//                         badlist_init(&bl);
//                         unsigned long long scanned2 = 0;
//                         unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, to_check, &scanned2);
//                         if (bad == (unsigned long long)-1)
//                         {
//                             fprintf(stderr, "[ERR] failed to compare with index\n");
//                         }
//                         else
//                         {
//                             fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned2, bad);
//                             if (bl.count > 0)
//                             {
//                                 fprintf(stderr, "[DETAIL] bad line list: ");
//                                 for (size_t i = 0; i < bl.count; i++)
//                                 {
//                                     fprintf(stderr, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                                 }
//                                 fputc('\n', stderr);
//                                 char msg[4096];
//                                 size_t pos = 0;
//                                 pos += snprintf(msg + pos, sizeof(msg) - pos, "Tampered lines: ");
//                                 for (size_t i = 0; i < bl.count && pos < sizeof(msg) - 32; i++)
//                                     pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                                 trigger_alarm(msg);
//                             }
//                             else
//                             {
//                                 fprintf(stderr, "[DETAIL] No mismatched lhash in first %llu lines compared to current idx\n", to_check);
//                             }
//                         }
//                         badlist_free(&bl);
//                     }
//                 }
//                 else
//                 {
//                     fprintf(stderr, "[WARN] cannot compute sm3 of %s\n", idx_path);
//                 }
//             }
//             else
//             {
//                 fprintf(stderr, "[WARN] index_sm3 or idx_path not available; cannot do fine-grained diff\n");
//             }

//             free_seal(&S);
//             if (idx_path)
//                 free(idx_path);
//             gm_crypto_cleanup();
//             return 1;
//         }
//     }
//     else
//     {
//         /* to_check == 0 (records==0): recomputed chain is zero */
//         unsigned char zero[32] = {0};
//         char zero_hex[65];
//         hexdump_buf(zero, 32, zero_hex, sizeof(zero_hex));
//         if (strcmp(zero_hex, S.chain) != 0)
//         {
//             trigger_alarm("Chain mismatch for empty chain!");
//             fprintf(stderr, "[FAIL] chain mismatch for empty chain\n");
//             free_seal(&S);
//             if (idx_path)
//                 free(idx_path);
//             gm_crypto_cleanup();
//             return 1;
//         }
//     }

//     /* 到这里前 to_check(=seal_records) 行 chain 匹配 -> 验证 index_sm3 是否匹配当前 idx（警告但仍继续）*/
//     if (idx_path)
//     {
//         char idx_sm3_now[65];
//         if (sm3_file_hex(idx_path, idx_sm3_now) == 0)
//         {
//             if (strcmp(idx_sm3_now, S.index_sm3) != 0)
//             {
//                 trigger_alarm("Index file hash mismatch: index may be tampered!");
//                 fprintf(stderr, "[ALERT] index_sm3 mismatch: %s (seal) vs %s (current)\n", S.index_sm3, idx_sm3_now);
//             }
//             else
//             {
//                 fprintf(stderr, "[DEBUG] index_sm3 matches\n");
//             }
//         }
//         else
//         {
//             fprintf(stderr, "[WARN] cannot compute sm3 of %s\n", idx_path);
//         }
//     }

//     /* 验证证书指纹 */
//     size_t cert_len = 0;
//     unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der)
//         diee("cert b64 decode failed");
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert)
//         diee("d2i_X509 failed");
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub)
//         diee("X509_get_pubkey failed");
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey)
//     {
//         trigger_alarm("Public key is not EC key!");
//         diee("pub is not EC");
//     }
//     const EC_GROUP *grp = EC_KEY_get0_group(eckey);
//     if (!grp || EC_GROUP_get_curve_name(grp) != NID_sm2)
//     {
//         trigger_alarm("Certificate is not SM2 type!");
//         diee("[ERROR] 证书不是SM2");
//     }

//     /* 计算 cert 指纹 */
//     unsigned char *der2 = NULL;
//     int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0)
//         diee("i2d_X509 failed");
//     unsigned char md[EVP_MAX_MD_SIZE];
//     unsigned int mdlen = 0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//         diee("ctx");
//     if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, md, &mdlen) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der2);
//         diee("fingerprint digest failed");
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der2);
//     char fpr_hex[65];
//     hexdump_buf(md, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0)
//     {
//         trigger_alarm("Certificate fingerprint mismatch detected!");
//         diee("[FAIL] key_fpr mismatch");
//     }

//     /* 构造 canonical 并验签 */
//     char *canon = build_canonical(&S);
//     if (!canon)
//         diee("asprintf");
//     dump_hex_prefix("verify", (const unsigned char *)canon, strlen(canon));
//     size_t siglen = 0;
//     unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig)
//         diee("sig b64 decode failed");

//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig);
//     free(canon);
//     EVP_PKEY_free(pub);
//     X509_free(cert);
//     gm_crypto_cleanup();

//     if (v == 1)
//     {
//         fprintf(stderr, "[OK] verify success\n");
//         if (log_lines > seal_records)
//         {
//             fprintf(stderr, "[WARN] audit.log has %llu lines but seal covers only %llu records; last %llu lines are not covered by this seal\n",
//                     log_lines, seal_records, log_lines - seal_records);
//         }
//         free_seal(&S);
//         if (idx_path)
//             free(idx_path);
//         return 0;
//     }
//     else if (v == 0)
//     {
//         trigger_alarm("Signature mismatch detected!");
//         diee("[FAIL] signature invalid");
//     }
//     else
//     {
//         trigger_alarm("Signature verification error!");
//         diee("[FAIL] verify error");
//     }

//     /* 不会到这里 */
//     free_seal(&S);
//     if (idx_path)
//         free(idx_path);
//     return 0;
// }

//----解决.isx  .seal和audit.log行数不一致问题 版本3
/* audit-verify.c (modified)
   Verify an audit.log.seal produced by gmseal-plugin.c above.
   Uses identical canonical string format and identical line normalization.
*/

// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <string.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <sys/stat.h>
// #include <limits.h>

// #include <openssl/evp.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/err.h>

// #include "alarm.c"
// #include "../../lib/gm_crypto.h"

// #define SM2_ID "1234567812345678"
// static const char HEX_CHARS[] = "0123456789abcdef";

// static void diee(const char *m)
// {
//     fprintf(stderr, "%s\n", m);
//     exit(1);
// }

// static void hexdump_buf(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = HEX_CHARS[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = HEX_CHARS[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// /* base64 decode */
// static unsigned char *b64_decode(const char *b64, size_t *out_len)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m)
//     {
//         if (b)
//             BIO_free(b);
//         if (m)
//             BIO_free(m);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     if (!buf)
//     {
//         BIO_free_all(b);
//         return NULL;
//     }
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0)
//     {
//         free(buf);
//         return NULL;
//     }
//     *out_len = (size_t)n;
//     return buf;
// }

// /* sm3 of a whole file -> hex */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//     {
//         fclose(f);
//         return -1;
//     }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         fclose(f);
//         return -1;
//     }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
//     {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(f);
//             return -1;
//         }
//     }
//     fclose(f);
//     unsigned char md[32];
//     unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     hexdump_buf(md, 32, out_hex, 65);
//     return 0;
// }

// static int count_lines(const char *path, unsigned long long *out_lines)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     *out_lines = ln;
//     return 0;
// }

// /* normalize: trim trailing CR/LF */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//         len--;
//     return len;
// }

// /* parse seal (same fields used in producer) */
// struct seal
// {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// static char *trimws(char *s)
// {
//     while (*s == ' ' || *s == '\t')
//         s++;
//     size_t n = strlen(s);
//     while (n > 0 && (s[n - 1] == ' ' || s[n - 1] == '\t' || s[n - 1] == '\r' || s[n - 1] == '\n'))
//         s[--n] = 0;
//     return s;
// }
// static void trimnl(char *s)
// {
//     size_t n = strlen(s);
//     while (n && (s[n - 1] == '\n' || s[n - 1] == '\r'))
//         s[--n] = 0;
// }

// static int parse_seal(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1)
//     {
//         if (line[0] == '#' || line[0] == 0)
//             continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq)
//             continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq + 1);
//         fprintf(stderr, "[DEBUG] Parsing: '%s' = '%s'\n", k, v);
//         if (strcmp(k, "version") == 0)
//             S->version = strdup(v);
//         else if (strcmp(k, "hash") == 0)
//             S->hash = strdup(v);
//         else if (strcmp(k, "sign") == 0)
//             S->sign = strdup(v);
//         else if (strcmp(k, "sm2_id") == 0)
//             S->sm2_id = strdup(v);
//         else if (strcmp(k, "path") == 0)
//             S->path = strdup(v);
//         else if (strcmp(k, "records") == 0)
//             S->records = strdup(v);
//         else if (strcmp(k, "chain") == 0)
//             S->chain = strdup(v);
//         else if (strcmp(k, "key_fpr") == 0)
//             S->key_fpr = strdup(v);
//         else if (strcmp(k, "cert_der_base64") == 0)
//             S->cert_b64 = strdup(v);
//         else if (strcmp(k, "sig_base64") == 0)
//             S->sig_b64 = strdup(v);
//         else if (strcmp(k, "index_sm3") == 0)
//             S->index_sm3 = strdup(v);
//     }
//     free(line);
//     fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
//         return 0;
//     return -1;
// }
// static void free_seal(struct seal *S)
// {
//     if (!S)
//         return;
//     free(S->version);
//     free(S->hash);
//     free(S->sign);
//     free(S->sm2_id);
//     free(S->path);
//     free(S->records);
//     free(S->chain);
//     free(S->key_fpr);
//     free(S->index_sm3);
//     free(S->cert_b64);
//     free(S->sig_b64);
// }

// /* canonical builder exactly same as producer */
// static char *build_canonical(const struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//                  "version=%s\n"
//                  "hash=%s\n"
//                  "sign=%s\n"
//                  "sm2_id=%s\n"
//                  "path=%s\n"
//                  "records=%s\n"
//                  "chain=%s\n"
//                  "key_fpr=%s\n"
//                  "index_sm3=%s\n",
//                  S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0)
//         return NULL;
//     return buf;
// }

// /* Bad list helper */
// typedef struct
// {
//     unsigned long long *lines;
//     size_t count, cap;
// } BadList;
// static void badlist_init(BadList *b)
// {
//     if (!b)
//         return;
//     b->lines = NULL;
//     b->count = b->cap = 0;
// }
// static void badlist_free(BadList *b)
// {
//     if (!b)
//         return;
//     free(b->lines);
//     b->lines = NULL;
//     b->count = b->cap = 0;
// }
// static void badlist_add(BadList *b, unsigned long long ln)
// {
//     if (!b)
//         return;
//     if (b->count == b->cap)
//     {
//         size_t nc = b->cap ? b->cap * 2 : 64;
//         unsigned long long *p = realloc(b->lines, nc * sizeof(*p));
//         if (!p)
//             return;
//         b->lines = p;
//         b->cap = nc;
//     }
//     b->lines[b->count++] = ln;
// }

// /* compare with index for first max_lines (0==all). return bad count or (unsigned long long)-1 on error */
// static unsigned long long compare_with_index_limit(const char *log_path, const char *idx_path, BadList *out_bad, unsigned long long max_lines, unsigned long long *scanned_lines)
// {
//     if (scanned_lines)
//         *scanned_lines = 0;
//     if (out_bad)
//         badlist_init(out_bad);
//     FILE *lf = fopen(log_path, "rb");
//     FILE *ix = fopen(idx_path, "r");
//     if (!lf || !ix)
//     {
//         if (lf)
//             fclose(lf);
//         if (ix)
//             fclose(ix);
//         return (unsigned long long)-1;
//     }

//     /* skip header until first "i=" */
//     char ibuf[1024];
//     long back_pos = 0;
//     while (fgets(ibuf, sizeof(ibuf), ix))
//     {
//         if (ibuf[0] == '#')
//             continue;
//         if (strncmp(ibuf, "i=", 2) == 0)
//         {
//             back_pos = ftell(ix) - (long)strlen(ibuf);
//             break;
//         }
//     }
//     if (back_pos > 0)
//         fseek(ix, back_pos, SEEK_SET);

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//     {
//         fclose(lf);
//         fclose(ix);
//         return (unsigned long long)-1;
//     }

//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0, bad = 0;
//     static const char hex[] = "0123456789abcdef";

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         if (max_lines != 0 && ln > max_lines)
//             break;
//         if (!fgets(ibuf, sizeof(ibuf), ix))
//         {
//             bad++;
//             if (out_bad)
//                 badlist_add(out_bad, ln);
//             continue;
//         }
//         unsigned long long idx_ln = 0, off = 0;
//         char lhex[128] = {0}, chex[128] = {0};
//         if (sscanf(ibuf, "i=%llu off=%llu lhash=%64s chash=%64s", &idx_ln, &off, lhex, chex) != 4 || idx_ln != ln)
//         {
//             bad++;
//             if (out_bad)
//                 badlist_add(out_bad, ln);
//             continue;
//         }

//         /* normalize line buffer */
//         size_t normlen = normalize_line(line, (size_t)n);

//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char L[32];
//         unsigned int lm = 0;
//         int ok1 = ctx &&
//                   EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1 &&
//                   EVP_DigestFinal_ex(ctx, L, &lm) == 1 && lm == 32;
//         if (!ok1)
//         {
//             if (ctx)
//                 EVP_MD_CTX_free(ctx);
//             free(line);
//             fclose(lf);
//             fclose(ix);
//             return (unsigned long long)-1;
//         }
//         EVP_MD_CTX_free(ctx);
//         char Lhex[65];
//         for (int i = 0; i < 32; i++)
//         {
//             Lhex[i * 2] = hex[(L[i] >> 4) & 0xF];
//             Lhex[i * 2 + 1] = hex[L[i] & 0xF];
//         }
//         Lhex[64] = 0;
//         if (strcmp(Lhex, lhex) != 0)
//         {
//             bad++;
//             if (out_bad)
//                 badlist_add(out_bad, ln);
//         }
//     }

//     free(line);
//     fclose(lf);
//     fclose(ix);
//     if (scanned_lines)
//         *scanned_lines = (max_lines == 0) ? ln : (ln > max_lines ? max_lines : ln);
//     return bad;
// }

// int main(int argc, char **argv)
// {
//     if (argc != 2)
//     {
//         fprintf(stderr, "Usage: %s /var/log/audit/audit.log.seal\n", argv[0]);
//         return 1;
//     }

//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();
//     if (gm_crypto_init() != 0)
//         diee("gm init failed");
//     if (!EVP_get_digestbyname("sm3"))
//         diee("[ERROR] no sm3");

//     struct seal S;
//     if (parse_seal(argv[1], &S) != 0)
//         diee("parse seal failed");
//     if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0)
//         diee("[ERROR] seal not sm2/sm3");
//     if (strcmp(S.sm2_id, SM2_ID) != 0)
//         diee("[ERROR] SM2_ID mismatch");

//     unsigned long long seal_records = 0;
//     if (S.records)
//         seal_records = strtoull(S.records, NULL, 10);

//     unsigned long long log_lines = 0;
//     if (count_lines(S.path, &log_lines) != 0)
//     {
//         trigger_alarm("Cannot open audit log");
//         diee("[ERROR] cannot count lines of audit.log");
//     }
//     fprintf(stderr, "[DEBUG] audit.log has %llu lines; seal.records=%llu\n", log_lines, seal_records);

//     char *idx_path = NULL;
//     if (asprintf(&idx_path, "%s.idx", S.path) < 0)
//         idx_path = NULL;

//     if (log_lines < seal_records)
//     {
//         trigger_alarm("Audit log truncated: seal expects more records than present");
//         fprintf(stderr, "[ALARM] Audit log truncated: seal expects %llu records but file has %llu lines\n", seal_records, log_lines);
//         if (idx_path)
//         {
//             BadList bl;
//             badlist_init(&bl);
//             unsigned long long scanned = 0;
//             unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, log_lines, &scanned);
//             if (bad == (unsigned long long)-1)
//             {
//                 fprintf(stderr, "[ERR] failed to compare with index\n");
//             }
//             else
//             {
//                 fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned, bad);
//                 if (bl.count > 0)
//                 {
//                     fprintf(stderr, "[DETAIL] bad line list: ");
//                     for (size_t i = 0; i < bl.count; i++)
//                         fprintf(stderr, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                     fputc('\n', stderr);
//                     char msg[4096];
//                     size_t pos = 0;
//                     pos += snprintf(msg + pos, sizeof(msg) - pos, "Tampered lines: ");
//                     for (size_t i = 0; i < bl.count && pos < sizeof(msg) - 32; i++)
//                         pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                     trigger_alarm(msg);
//                 }
//             }
//             badlist_free(&bl);
//         }
//         else
//         {
//             fprintf(stderr, "[WARN] idx path not available\n");
//         }
//         free_seal(&S);
//         if (idx_path)
//             free(idx_path);
//         gm_crypto_cleanup();
//         return 1;
//     }

//     /* verify first seal_records lines */
//     unsigned long long to_check = seal_records;
//     if (to_check > 0)
//     {
//         unsigned char now_chain_bin[32];
//         unsigned long long scanned = 0;
//         /* compute chain over first to_check lines using normalized lines as producer did */
//         const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//         if (!sm3)
//             diee("no sm3");
//         unsigned char H[32] = {0};
//         FILE *lf = fopen(S.path, "rb");
//         if (!lf)
//             diee("cannot open audit.log for chain computation");
//         char *line = NULL;
//         size_t cap = 0;
//         ssize_t n;
//         unsigned long long ln = 0;
//         while (ln < to_check && (n = getline(&line, &cap, lf)) != -1)
//         {
//             ln++;
//             size_t normlen = normalize_line(line, (size_t)n);
//             EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//             unsigned int mdlen = 0;
//             if (!ctx || EVP_DigestInit_ex(ctx, sm3, NULL) != 1 || EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 || EVP_DigestUpdate(ctx, line, normlen) != 1 || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//             {
//                 if (ctx)
//                     EVP_MD_CTX_free(ctx);
//                 free(line);
//                 fclose(lf);
//                 diee("chain compute failed");
//             }
//             EVP_MD_CTX_free(ctx);
//         }
//         free(line);
//         fclose(lf);
//         hexdump_buf(H, 32, (char *)now_chain_bin, 65); /* careful: now_chain_bin sized 32, but we'll produce hex below */
//         /* now H contains binary chain; compare hex */
//         char now_chain_hex[65];
//         hexdump_buf(H, 32, now_chain_hex, sizeof(now_chain_hex));
//         if (strcmp(now_chain_hex, S.chain) != 0)
//         {
//             trigger_alarm("Chain mismatch detected: audit log may be tampered!");
//             fprintf(stderr, "[FAIL] chain mismatch (文件被改动?)\n");
//             fprintf(stderr, "[DEBUG] recomputed chain (first %llu lines) = %s\n", to_check, now_chain_hex);

//             if (S.index_sm3 && *S.index_sm3 && idx_path)
//             {
//                 char idx_sm3_now[65];
//                 if (sm3_file_hex(idx_path, idx_sm3_now) == 0)
//                 {
//                     if (strcmp(idx_sm3_now, S.index_sm3) != 0)
//                     {
//                         trigger_alarm("Index file hash mismatch: index may also be tampered!");
//                         fprintf(stderr, "[ALERT] index_sm3 mismatch: %s (seal) vs %s (current)\n", S.index_sm3, idx_sm3_now);
//                     }
//                     else
//                     {
//                         BadList bl;
//                         badlist_init(&bl);
//                         unsigned long long scanned2 = 0;
//                         unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, to_check, &scanned2);
//                         if (bad == (unsigned long long)-1)
//                             fprintf(stderr, "[ERR] failed to compare with index\n");
//                         else
//                         {
//                             fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned2, bad);
//                             if (bl.count > 0)
//                             {
//                                 fprintf(stderr, "[DETAIL] bad line list: ");
//                                 for (size_t i = 0; i < bl.count; i++)
//                                     fprintf(stderr, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                                 fputc('\n', stderr);
//                                 char msg[4096];
//                                 size_t pos = 0;
//                                 pos += snprintf(msg + pos, sizeof(msg) - pos, "Tampered lines: ");
//                                 for (size_t i = 0; i < bl.count && pos < sizeof(msg) - 32; i++)
//                                     pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s", bl.lines[i], (i + 1 < bl.count) ? "," : "");
//                                 trigger_alarm(msg);
//                             }
//                             else
//                             {
//                                 fprintf(stderr, "[DETAIL] No mismatched lhash in first %llu lines compared to current idx\n", to_check);
//                             }
//                         }
//                         badlist_free(&bl);
//                     }
//                 }
//                 else
//                 {
//                     fprintf(stderr, "[WARN] cannot compute sm3 of %s\n", idx_path);
//                 }
//             }
//             else
//             {
//                 fprintf(stderr, "[WARN] index_sm3 or idx_path not available; cannot do fine-grained diff\n");
//             }

//             free_seal(&S);
//             if (idx_path)
//                 free(idx_path);
//             gm_crypto_cleanup();
//             return 1;
//         }
//     }
//     else
//     {
//         /* records==0 - check that chain is zero */
//         unsigned char zero[32] = {0};
//         char zerohex[65];
//         hexdump_buf(zero, 32, zerohex, sizeof(zerohex));
//         if (strcmp(zerohex, S.chain) != 0)
//         {
//             trigger_alarm("Chain mismatch for empty chain");
//             diee("[FAIL] chain mismatch for empty chain");
//         }
//     }

//     /* index_sm3 compare (warning only) */
//     if (idx_path)
//     {
//         char idx_sm3_now[65];
//         if (sm3_file_hex(idx_path, idx_sm3_now) == 0)
//         {
//             if (strcmp(idx_sm3_now, S.index_sm3) != 0)
//             {
//                 trigger_alarm("Index file hash mismatch: index may be tampered!");
//                 fprintf(stderr, "[ALERT] index_sm3 mismatch: %s (seal) vs %s (current)\n", S.index_sm3, idx_sm3_now);
//             }
//             else
//                 fprintf(stderr, "[DEBUG] index_sm3 matches\n");
//         }
//         else
//             fprintf(stderr, "[WARN] cannot compute sm3 of %s\n", idx_path);
//     }

//     /* verify certificate fingerprint */
//     size_t cert_len = 0;
//     unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der)
//         diee("cert b64 decode failed");
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert)
//         diee("d2i_X509 failed");
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub)
//         diee("X509_get_pubkey failed");
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey)
//     {
//         trigger_alarm("Public key is not EC key!");
//         diee("pub is not EC");
//     }
//     const EC_GROUP *grp = EC_KEY_get0_group(eckey);
//     if (!grp || EC_GROUP_get_curve_name(grp) != NID_sm2)
//     {
//         trigger_alarm("Certificate is not SM2 type!");
//         diee("[ERROR] cert not SM2");
//     }

//     unsigned char *der2 = NULL;
//     int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0)
//         diee("i2d_X509 failed");
//     unsigned char md[EVP_MAX_MD_SIZE];
//     unsigned int mdlen = 0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//         diee("ctx");
//     if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 || EVP_DigestUpdate(ctx, der2, der2len) != 1 || EVP_DigestFinal_ex(ctx, md, &mdlen) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der2);
//         diee("fingerprint digest failed");
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der2);
//     char fpr_hex[65];
//     hexdump_buf(md, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0)
//     {
//         trigger_alarm("Certificate fingerprint mismatch detected!");
//         diee("[FAIL] key_fpr mismatch");
//     }

//     /* verify signature */
//     char *canon = build_canonical(&S);
//     if (!canon)
//         diee("asprintf");
//     size_t siglen = 0;
//     unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig)
//         diee("sig b64 decode failed");

//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig);
//     free(canon);
//     EVP_PKEY_free(pub);
//     X509_free(cert);
//     gm_crypto_cleanup();

//     if (v == 1)
//     {
//         fprintf(stderr, "[OK] verify success\n");
//         if (log_lines > seal_records)
//         {
//             fprintf(stderr, "[WARN] audit.log has %llu lines but seal covers only %llu records; last %llu lines are not covered by this seal\n", log_lines, seal_records, log_lines - seal_records);
//         }
//         free_seal(&S);
//         if (idx_path)
//             free(idx_path);
//         return 0;
//     }
//     else if (v == 0)
//     {
//         trigger_alarm("Signature mismatch detected!");
//         diee("[FAIL] signature invalid");
//     }
//     else
//     {
//         trigger_alarm("Signature verification error!");
//         diee("[FAIL] verify error");
//     }
//     /* not reached */
//     free_seal(&S);
//     if (idx_path)
//         free(idx_path);
//     return 0;
// }

//----解决.isx  .seal和audit.log行数不一致问题 版本4
/* ---------- audit-verify.c (修正版) ---------- */
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <string.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <sys/stat.h>
// #include <limits.h>

// #include <openssl/evp.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/err.h>

// #include "alarm.c"                 // 你已有的 trigger_alarm 实现
// #include "../../lib/gm_crypto.h"   // gm_sm2_verify

// #define SM2_ID "1234567812345678"
// static const char HEX_CHARS[] = "0123456789abcdef";

// /* ----------------- 小工具 ----------------- */

// static void diee(const char *m)
// {
//     fprintf(stderr, "%s\n", m);
//     exit(1);
// }

// static void hexdump_buf(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     if (outlen < len*2 + 1) return;
//     for (size_t i = 0; i < len; i++) {
//         out[i*2]     = HEX_CHARS[(buf[i] >> 4) & 0xF];
//         out[i*2 + 1] = HEX_CHARS[buf[i] & 0xF];
//     }
//     out[len*2] = 0;
// }

// /* base64 decode (no NL handling) */
// static unsigned char *b64_decode(const char *b64, size_t *out_len)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *out_len = (size_t)n;
//     return buf;
// }

// /* normalize line: trim trailing CR/LF, return normalized length */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) len--;
//     return len;
// }

// /* 计算文件 SM3 -> hex (整文件) */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump_buf(md, 32, out_hex, 65);
//     return 0;
// }

// /* 统计文件行数 */
// static int count_lines(const char *path, unsigned long long *out_lines)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1) ln++;
//     free(line);
//     fclose(f);
//     *out_lines = ln;
//     return 0;
// }

// /* 计算文件前 N 行的 chain（SM3），同时输出实际扫描行数（<= N）
//    使用 normalize_line 与插件保持一致。 */
// static int sm3_chain_file_limit(const char *path, unsigned long long limit_lines, unsigned char out32[32], unsigned long long *out_scanned)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;

//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1) {
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, line, normlen) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         unsigned int mdlen = 0;
//         if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     if (out_scanned) *out_scanned = scanned;
//     return 0;
// }

// /* ----------------- Seal 解析 ----------------- */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// static void trimnl(char *s) {
//     size_t n = strlen(s);
//     while (n && (s[n-1]=='\n' || s[n-1]=='\r')) s[--n] = 0;
// }
// static char *trimws(char *s) {
//     while (*s == ' ' || *s == '\t') s++;
//     size_t n = strlen(s);
//     while (n > 0 && (s[n-1] == ' ' || s[n-1] == '\t' || s[n-1] == '\r' || s[n-1] == '\n')) s[--n]=0;
//     return s;
// }

// static int parse_seal(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq+1);
//         fprintf(stderr, "[DEBUG] Parsing: '%s' = '%s'\n", k, v);
//         if (strcmp(k,"version")==0) S->version = strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash = strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign = strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id = strdup(v);
//         else if (strcmp(k,"path")==0) S->path = strdup(v);
//         else if (strcmp(k,"records")==0) S->records = strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain = strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr = strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64 = strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64 = strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3 = strdup(v);
//     }
//     free(line);
//     fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
//         return 0;
//     return -1;
// }

// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }

// /* 构造 canonical 用于验签（与 seal/producer 保持一致） */
// static char *build_canonical(const struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//         "version=%s\n"
//         "hash=%s\n"
//         "sign=%s\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%s\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
//     return buf;
// }

// static void dump_hex_prefix(const char *tag, const unsigned char *buf, size_t len)
// {
//     size_t n = len > 64 ? 64 : len;
//     fprintf(stderr, "[DBG] %s canon len=%zu hex:", tag, len);
//     for (size_t i=0;i<n;i++) fprintf(stderr,"%02x",buf[i]);
//     if (len>n) fprintf(stderr,"...");
//     fputc('\n', stderr);
// }

// /* ----------------- 读取 idx 尾部 ----------------- */
// static int read_idx_tail(const char *idx_path, unsigned long long *out_i, char chash_hex[65])
// {
//     FILE *f = fopen(idx_path, "r");
//     if (!f) return -1;
//     char buf[1024];
//     unsigned long long last_i = 0;
//     char last_chash[65] = {0};

//     while (fgets(buf, sizeof(buf), f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long i=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%64s chash=%64s", &i, &off, lhex, chex) == 4) {
//             last_i = i;
//             strncpy(last_chash, chex, sizeof(last_chash)-1);
//         }
//     }
//     fclose(f);
//     if (last_i == 0) return -1;
//     if (out_i) *out_i = last_i;
//     strncpy(chash_hex, last_chash, 65);
//     return 0;
// }

// /* ----------------- 索引比较（带行限制） ----------------- */
// typedef struct {
//     unsigned long long *lines;
//     size_t count, cap;
// } BadList;
// static void badlist_init(BadList *b) { b->lines = NULL; b->count = b->cap = 0; }
// static void badlist_free(BadList *b) { free(b->lines); b->lines = NULL; b->count = b->cap = 0; }
// static void badlist_add(BadList *b, unsigned long long ln) {
//     if (!b) return;
//     if (b->count == b->cap) {
//         size_t nc = b->cap ? b->cap * 2 : 64;
//         unsigned long long *p = realloc(b->lines, nc * sizeof(*p));
//         if (!p) return;
//         b->lines = p; b->cap = nc;
//     }
//     b->lines[b->count++] = ln;
// }

// static unsigned long long compare_with_index_limit(const char *log_path, const char *idx_path,
//                                                    BadList *out_bad,
//                                                    unsigned long long max_lines,
//                                                    unsigned long long *scanned_lines)
// {
//     if (scanned_lines) *scanned_lines = 0;
//     if (out_bad) badlist_init(out_bad);
//     FILE *lf = fopen(log_path,"rb");
//     FILE *ix = fopen(idx_path,"r");
//     if (!lf || !ix) { if (lf) fclose(lf); if (ix) fclose(ix); return (unsigned long long)-1; }

//     /* 跳过 idx 头，直到 i= */
//     char ibuf[1024];
//     long back_pos = 0;
//     while (fgets(ibuf, sizeof(ibuf), ix)) {
//         if (ibuf[0] == '#') continue;
//         if (strncmp(ibuf, "i=", 2) == 0) {
//             back_pos = ftell(ix) - (long)strlen(ibuf);
//             break;
//         }
//     }
//     if (back_pos > 0) fseek(ix, back_pos, SEEK_SET);

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { fclose(lf); fclose(ix); return (unsigned long long)-1; }

//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, bad = 0;
//     static const char hex[] = "0123456789abcdef";

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         if (max_lines != 0 && ln > max_lines) break;

//         if (!fgets(ibuf, sizeof(ibuf), ix)) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//             continue;
//         }
//         unsigned long long idx_ln=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(ibuf, "i=%llu off=%llu lhash=%64s chash=%64s", &idx_ln, &off, lhex, chex) != 4 || idx_ln != ln) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//             continue;
//         }

//         /* normalize same as plugin */
//         size_t normlen = normalize_line(line, (size_t)n);

//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         int ok1 = ctx &&
//                   EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1 &&
//                   EVP_DigestFinal_ex(ctx, L, &lm) == 1 && lm == 32;
//         if (!ok1) {
//             if (ctx) EVP_MD_CTX_free(ctx);
//             fclose(lf); fclose(ix); free(line);
//             return (unsigned long long)-1;
//         }
//         EVP_MD_CTX_free(ctx);
//         char Lhex[65];
//         for (int i=0;i<32;i++) { Lhex[i*2] = hex[(L[i] >> 4) & 0xF]; Lhex[i*2+1] = hex[L[i] & 0xF]; }
//         Lhex[64]=0;
//         if (strcmp(Lhex, lhex) != 0) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//         }
//     }

//     free(line);
//     fclose(lf);
//     fclose(ix);
//     if (scanned_lines) *scanned_lines = (max_lines == 0) ? ln : (ln > max_lines ? max_lines : ln);
//     return bad;
// }

// /* ----------------- main ----------------- */

// int main(int argc, char **argv)
// {
//     if (argc != 2) {
//         fprintf(stderr, "Usage: %s /var/log/audit/audit.log.seal\n", argv[0]);
//         return 1;
//     }

//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();
//     if (gm_crypto_init() != 0) diee("gm init failed");
//     if (!EVP_get_digestbyname("sm3")) diee("[ERROR] 无 sm3");

//     struct seal S;
//     if (parse_seal(argv[1], &S) != 0) diee("parse seal failed");

//     if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0) diee("[ERROR] seal 非 sm2/sm3");
//     if (strcmp(S.sm2_id, SM2_ID) != 0) diee("[ERROR] SM2_ID 不匹配");

//     /* 解析 seal 中的 records */
//     unsigned long long seal_records = 0;
//     if (S.records) seal_records = strtoull(S.records, NULL, 10);

//     /* 读取 audit.log 实际行数（仅用于提示，不作为唯一依据） */
//     unsigned long long log_lines = 0;
//     if (count_lines(S.path, &log_lines) != 0) {
//         trigger_alarm("Cannot open audit log");
//         diee("[ERROR] cannot count lines of audit.log");
//     }
//     fprintf(stderr, "[DEBUG] audit.log has %llu lines; seal.records=%llu\n", log_lines, seal_records);

//     /* idx path */
//     char *idx_path = NULL;
//     if (asprintf(&idx_path, "%s.idx", S.path) < 0) idx_path = NULL;
//     if (!idx_path) diee("OOM");

//     /* 1) 先对比 index_sm3：确保 idx 未被篡改 */
//     int idx_ok = 0;
//     if (S.index_sm3 && *S.index_sm3 && access(idx_path, R_OK) == 0) {
//         char idx_sm3_now[65];
//         if (sm3_file_hex(idx_path, idx_sm3_now) == 0) {
//             if (strcmp(idx_sm3_now, S.index_sm3) == 0) {
//                 fprintf(stderr, "[DEBUG] index_sm3 matches\n");
//                 idx_ok = 1;
//             } else {
//                 trigger_alarm("Index file hash mismatch: index may be tampered!");
//                 fprintf(stderr, "[ALERT] index_sm3 mismatch: %s (seal) vs %s (current)\n", S.index_sm3, idx_sm3_now);
//                 /* idx 被篡改，则我们不能信任 idx，后续只能 fallback 到对 audit.log 的链重算 */
//             }
//         } else {
//             fprintf(stderr, "[WARN] cannot compute sm3 of %s\n", idx_path);
//         }
//     } else {
//         fprintf(stderr, "[WARN] index_sm3 not present or idx file missing\n");
//     }

//     /* 2) 如果 idx_ok，再核对 idx 的最后一行 chash 是否等于 seal.chain，i 是否等于 seal.records */
//     int idx_tail_ok = 0;
//     if (idx_ok) {
//         unsigned long long idx_last_i = 0;
//         char idx_last_chash[65] = {0};
//         if (read_idx_tail(idx_path, &idx_last_i, idx_last_chash) == 0) {
//             if (idx_last_i == seal_records && strcmp(idx_last_chash, S.chain) == 0) {
//                 fprintf(stderr, "[DEBUG] idx tail matches seal: i=%llu chash=%s\n", idx_last_i, idx_last_chash);
//                 idx_tail_ok = 1;
//             } else {
//                 trigger_alarm("Seal/Index mismatch: records or chain differ!");
//                 fprintf(stderr, "[ALERT] idx tail vs seal mismatch: idx_i=%llu seal_records=%llu; idx_chash=%s vs seal_chain=%s\n",
//                         idx_last_i, seal_records, idx_last_chash, S.chain);
//             }
//         } else {
//             fprintf(stderr, "[WARN] cannot read idx tail\n");
//         }
//     }

//     /* 3) 如果 idx 完整可信且 tail 匹配 seal.chain，我们就直接验签通过（不再硬性要求重算 audit.log 的链也相同） */
//     int chain_trusted_by_idx = (idx_ok && idx_tail_ok);

//     /* 4) 构造 canonical 并验签（与 seal 保持一致） */
//     size_t cert_len = 0;
//     unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) diee("cert b64 decode failed");
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) diee("d2i_X509 failed");
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) diee("X509_get_pubkey failed");
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey) { trigger_alarm("Public key is not EC key!"); diee("pub is not EC"); }
//     const EC_GROUP *grp = EC_KEY_get0_group(eckey);
//     if (!grp || EC_GROUP_get_curve_name(grp) != NID_sm2) { trigger_alarm("Certificate is not SM2 type!"); diee("[ERROR] 证书不是SM2"); }

//     /* 计算 cert 指纹 */
//     unsigned char *der2 = NULL;
//     int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0) diee("i2d_X509 failed");
//     unsigned char md[EVP_MAX_MD_SIZE]; unsigned int mdlen = 0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) diee("ctx");
//     if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, md, &mdlen) != 1) {
//         EVP_MD_CTX_free(ctx); OPENSSL_free(der2); diee("fingerprint digest failed");
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump_buf(md, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         trigger_alarm("Certificate fingerprint mismatch detected!");
//         diee("[FAIL] key_fpr mismatch");
//     }

//     char *canon = build_canonical(&S);
//     if (!canon) diee("asprintf");
//     dump_hex_prefix("verify", (const unsigned char *)canon, strlen(canon));
//     size_t siglen = 0;
//     unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) diee("sig b64 decode failed");
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig);
//     free(canon);

//     if (v != 1) {
//         EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//         if (v == 0) { trigger_alarm("Signature mismatch detected!"); diee("[FAIL] signature invalid"); }
//         else        { trigger_alarm("Signature verification error!"); diee("[FAIL] verify error"); }
//     }

//     /* 验签 OK 到这里 */
//     if (chain_trusted_by_idx) {
//         fprintf(stderr, "[OK] verify success (by idx+seal)\n");

//         /* 做日志 vs idx 的逐行比对（使用 normalize） */
//         BadList bl; badlist_init(&bl);
//         unsigned long long scanned2 = 0;
//         unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, seal_records, &scanned2);
//         if (bad == (unsigned long long)-1) {
//             fprintf(stderr, "[WARN] cannot compare audit.log vs idx (skip)\n");
//         } else if (bad == seal_records && seal_records > 0) {
//             /* 全部不匹配——这极可能是 ENRICHED/RAW 格式差异或 idx 与 log 字符序列不同(例如某些转码) */
//             fprintf(stderr, "[WARN] All %llu lines differ between audit.log and idx lhash.\n", seal_records);
//             fprintf(stderr, "       This usually means auditd writes ENRICHED logs while plugin hashes RAW (stdin).\n");
//             fprintf(stderr, "       To make byte-identical, set in /etc/audit/auditd.conf: log_format = RAW, then restart auditd.\n");
//         } else if (bad > 0) {
//             fprintf(stderr, "[ALARM] Found %llu mismatched lines (suspect tampering). First few: ", bad);
//             size_t max_show = bl.count < 20 ? bl.count : 20;
//             for (size_t i=0;i<max_show;i++) fprintf(stderr, "%llu%s", bl.lines[i], (i+1<max_show)?",":"");
//             fputc('\n', stderr);
//             char msg[4096]; size_t pos=0;
//             pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
//             for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//             trigger_alarm(msg);
//         } else {
//             fprintf(stderr, "[DEBUG] audit.log bytes match idx for first %llu lines\n", scanned2);
//         }
//         badlist_free(&bl);

//         if (log_lines > seal_records) {
//             fprintf(stderr, "[WARN] audit.log has %llu lines but seal covers only %llu records; last %llu lines are not covered by this seal\n",
//                     log_lines, seal_records, log_lines - seal_records);
//         }

//         EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//         return 0;
//     }

//     /* 走到这里说明：idx_sm3 或 idx_tail 不匹配，我们无法信赖 idx。
//        回退：用 audit.log 的前 seal_records 行重算 chain，与 seal.chain 比较 */
//     if (seal_records > 0) {
//         unsigned char now_chain_bin[32]; unsigned long long scanned = 0;
//         if (sm3_chain_file_limit(S.path, seal_records, now_chain_bin, &scanned) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//             diee("[ERR] failed to compute chain over first N lines");
//         }
//         char now_chain_hex[65]; hexdump_buf(now_chain_bin, 32, now_chain_hex, sizeof(now_chain_hex));
//         if (strcmp(now_chain_hex, S.chain) != 0) {
//             trigger_alarm("Chain mismatch detected: audit log may be tampered!");
//             fprintf(stderr, "[FAIL] chain mismatch (文件被改动?)\n");
//             fprintf(stderr, "[DEBUG] recomputed chain (first %llu lines) = %s\n", seal_records, now_chain_hex);

//             /* 尝试用 idx 找差异（尽管 idx_sm3 不匹配） */
//             if (access(idx_path, R_OK) == 0) {
//                 BadList bl; badlist_init(&bl);
//                 unsigned long long scanned2 = 0;
//                 unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, seal_records, &scanned2);
//                 if (bad != (unsigned long long)-1) {
//                     fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned2, bad);
//                     if (bl.count > 0) {
//                         fprintf(stderr, "[DETAIL] bad line list: ");
//                         for (size_t i=0;i<bl.count;i++) fprintf(stderr, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                         fputc('\n', stderr);
//                         char msg[4096]; size_t pos=0;
//                         pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
//                         for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                         trigger_alarm(msg);
//                     }
//                 }
//                 badlist_free(&bl);
//             }

//             EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//             return 1;
//         }
//     } else {
//         /* seal.records == 0，链为空 */
//     }

//     /* 到这里：idx 不可信，但 audit.log 的前 N 行重算链与 seal.chain 一致 + 验签过 → 成功 */
//     fprintf(stderr, "[OK] verify success (by recomputing chain from audit.log; idx untrusted)\n");
//     if (log_lines > seal_records) {
//         fprintf(stderr, "[WARN] audit.log has %llu lines but seal covers only %llu records; last %llu lines are not covered by this seal\n",
//                 log_lines, seal_records, log_lines - seal_records);
//     }
//     EVP_PKEY_free(pub); X509_free(cert);
//     gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//     return 0;
// }

//----解决.isx  .seal和audit.log行数不一致问题 版本5
/* ---------- audit-verify.c (修正版：统一 normalize，并增加诊断) ---------- */
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <string.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <sys/stat.h>
// #include <limits.h>

// #include <openssl/evp.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/err.h>

// #include "alarm.c"                 // 你已有的 trigger_alarm 实现
// #include "../../lib/gm_crypto.h"   // gm_sm2_verify

// #define SM2_ID "1234567812345678"
// static const char HEX_CHARS[] = "0123456789abcdef";

// /* ----------------- 小工具 ----------------- */

// static void diee(const char *m)
// {
//     fprintf(stderr, "%s\n", m);
//     exit(1);
// }

// static void hexdump_buf(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     if (outlen < len*2 + 1) return;
//     for (size_t i = 0; i < len; i++) {
//         out[i*2]     = HEX_CHARS[(buf[i] >> 4) & 0xF];
//         out[i*2 + 1] = HEX_CHARS[buf[i] & 0xF];
//     }
//     out[len*2] = 0;
// }

// /* base64 decode (no NL handling) */
// static unsigned char *b64_decode(const char *b64, size_t *out_len)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *out_len = (size_t)n;
//     return buf;
// }

// /* normalize line: trim trailing CR/LF, return normalized length */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) len--;
//     return len;
// }

// /* 计算文件 SM3 -> hex (整文件) */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump_buf(md, 32, out_hex, 65);
//     return 0;
// }

// /* 统计文件行数 */
// static int count_lines(const char *path, unsigned long long *out_lines)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1) ln++;
//     free(line);
//     fclose(f);
//     *out_lines = ln;
//     return 0;
// }

// /* 计算文件前 N 行的 chain（SM3），同时输出实际扫描行数（<= N）
//    使用 normalize_line 与插件保持一致。 */
// static int sm3_chain_file_limit(const char *path, unsigned long long limit_lines, unsigned char out32[32], unsigned long long *out_scanned)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;

//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1) {
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, line, normlen) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         unsigned int mdlen = 0;
//         if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     if (out_scanned) *out_scanned = scanned;
//     return 0;
// }

// /* ----------------- Seal 解析 ----------------- */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// static void trimnl(char *s) {
//     size_t n = strlen(s);
//     while (n && (s[n-1]=='\n' || s[n-1]=='\r')) s[--n] = 0;
// }
// static char *trimws(char *s) {
//     while (*s == ' ' || *s == '\t') s++;
//     size_t n = strlen(s);
//     while (n > 0 && (s[n-1] == ' ' || s[n-1] == '\t' || s[n-1] == '\r' || s[n-1] == '\n')) s[--n]=0;
//     return s;
// }

// static int parse_seal(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq+1);
//         fprintf(stderr, "[DEBUG] Parsing: '%s' = '%s'\n", k, v);
//         if (strcmp(k,"version")==0) S->version = strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash = strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign = strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id = strdup(v);
//         else if (strcmp(k,"path")==0) S->path = strdup(v);
//         else if (strcmp(k,"records")==0) S->records = strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain = strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr = strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64 = strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64 = strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3 = strdup(v);
//     }
//     free(line);
//     fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
//         return 0;
//     return -1;
// }

// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }

// /* 构造 canonical 用于验签（与 seal/producer 保持一致） */
// static char *build_canonical(const struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//         "version=%s\n"
//         "hash=%s\n"
//         "sign=%s\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%s\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
//     return buf;
// }

// static void dump_hex_prefix(const char *tag, const unsigned char *buf, size_t len)
// {
//     size_t n = len > 64 ? 64 : len;
//     fprintf(stderr, "[DBG] %s canon len=%zu hex:", tag, len);
//     for (size_t i=0;i<n;i++) fprintf(stderr,"%02x",buf[i]);
//     if (len>n) fprintf(stderr,"...");
//     fputc('\n', stderr);
// }

// /* ----------------- 读取 idx 尾部 ----------------- */
// static int read_idx_tail(const char *idx_path, unsigned long long *out_i, char chash_hex[65])
// {
//     FILE *f = fopen(idx_path, "r");
//     if (!f) return -1;
//     char buf[1024];
//     unsigned long long last_i = 0;
//     char last_chash[65] = {0};

//     while (fgets(buf, sizeof(buf), f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long i=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%64s chash=%64s", &i, &off, lhex, chex) == 4) {
//             last_i = i;
//             strncpy(last_chash, chex, sizeof(last_chash)-1);
//         }
//     }
//     fclose(f);
//     if (last_i == 0) return -1;
//     if (out_i) *out_i = last_i;
//     strncpy(chash_hex, last_chash, 65);
//     return 0;
// }

// /* ----------------- 索引比较（带行限制） ----------------- */
// typedef struct {
//     unsigned long long *lines;
//     size_t count, cap;
// } BadList;
// static void badlist_init(BadList *b) { b->lines = NULL; b->count = b->cap = 0; }
// static void badlist_free(BadList *b) { free(b->lines); b->lines = NULL; b->count = b->cap = 0; }
// static void badlist_add(BadList *b, unsigned long long ln) {
//     if (!b) return;
//     if (b->count == b->cap) {
//         size_t nc = b->cap ? b->cap * 2 : 64;
//         unsigned long long *p = realloc(b->lines, nc * sizeof(*p));
//         if (!p) return;
//         b->lines = p; b->cap = nc;
//     }
//     b->lines[b->count++] = ln;
// }

// static unsigned long long compare_with_index_limit(const char *log_path, const char *idx_path,
//                                                    BadList *out_bad,
//                                                    unsigned long long max_lines,
//                                                    unsigned long long *scanned_lines)
// {
//     if (scanned_lines) *scanned_lines = 0;
//     if (out_bad) badlist_init(out_bad);
//     FILE *lf = fopen(log_path,"rb");
//     FILE *ix = fopen(idx_path,"r");
//     if (!lf || !ix) { if (lf) fclose(lf); if (ix) fclose(ix); return (unsigned long long)-1; }

//     /* 跳过 idx 头，直到 i= */
//     char ibuf[1024];
//     long back_pos = 0;
//     while (fgets(ibuf, sizeof(ibuf), ix)) {
//         if (ibuf[0] == '#') continue;
//         if (strncmp(ibuf, "i=", 2) == 0) {
//             back_pos = ftell(ix) - (long)strlen(ibuf);
//             break;
//         }
//     }
//     if (back_pos > 0) fseek(ix, back_pos, SEEK_SET);

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { fclose(lf); fclose(ix); return (unsigned long long)-1; }

//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, bad = 0;
//     static const char hex[] = "0123456789abcdef";

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         if (max_lines != 0 && ln > max_lines) break;

//         if (!fgets(ibuf, sizeof(ibuf), ix)) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//             continue;
//         }
//         unsigned long long idx_ln=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(ibuf, "i=%llu off=%llu lhash=%64s chash=%64s", &idx_ln, &off, lhex, chex) != 4 || idx_ln != ln) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//             continue;
//         }

//         /* normalize same as plugin */
//         size_t normlen = normalize_line(line, (size_t)n);

//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         int ok1 = ctx &&
//                   EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1 &&
//                   EVP_DigestFinal_ex(ctx, L, &lm) == 1 && lm == 32;
//         if (!ok1) {
//             if (ctx) EVP_MD_CTX_free(ctx);
//             fclose(lf); fclose(ix); free(line);
//             return (unsigned long long)-1;
//         }
//         EVP_MD_CTX_free(ctx);
//         char Lhex[65];
//         for (int i=0;i<32;i++) { Lhex[i*2] = hex[(L[i] >> 4) & 0xF]; Lhex[i*2+1] = hex[L[i] & 0xF]; }
//         Lhex[64]=0;
//         if (strcmp(Lhex, lhex) != 0) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//         }
//     }

//     free(line);
//     fclose(lf);
//     fclose(ix);
//     if (scanned_lines) *scanned_lines = (max_lines == 0) ? ln : (ln > max_lines ? max_lines : ln);
//     return bad;
// }

// /* ----------------- main ----------------- */

// int main(int argc, char **argv)
// {
//     if (argc != 2) {
//         fprintf(stderr, "Usage: %s /var/log/audit/audit.log.seal\n", argv[0]);
//         return 1;
//     }

//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();
//     if (gm_crypto_init() != 0) diee("gm init failed");
//     if (!EVP_get_digestbyname("sm3")) diee("[ERROR] 无 sm3");

//     struct seal S;
//     if (parse_seal(argv[1], &S) != 0) diee("parse seal failed");

//     if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0) diee("[ERROR] seal 非 sm2/sm3");
//     if (strcmp(S.sm2_id, SM2_ID) != 0) diee("[ERROR] SM2_ID 不匹配");

//     /* 解析 seal 中的 records */
//     unsigned long long seal_records = 0;
//     if (S.records) seal_records = strtoull(S.records, NULL, 10);

//     /* 读取 audit.log 实际行数（仅用于提示，不作为唯一依据） */
//     unsigned long long log_lines = 0;
//     if (count_lines(S.path, &log_lines) != 0) {
//         trigger_alarm("Cannot open audit log");
//         diee("[ERROR] cannot count lines of audit.log");
//     }
//     fprintf(stderr, "[DEBUG] audit.log has %llu lines; seal.records=%llu\n", log_lines, seal_records);

//     /* idx path */
//     char *idx_path = NULL;
//     if (asprintf(&idx_path, "%s.idx", S.path) < 0) idx_path = NULL;
//     if (!idx_path) diee("OOM");

//     /* 1) 先对比 index_sm3：确保 idx 未被篡改 */
//     int idx_ok = 0;
//     if (S.index_sm3 && *S.index_sm3 && access(idx_path, R_OK) == 0) {
//         char idx_sm3_now[65];
//         if (sm3_file_hex(idx_path, idx_sm3_now) == 0) {
//             if (strcmp(idx_sm3_now, S.index_sm3) == 0) {
//                 fprintf(stderr, "[DEBUG] index_sm3 matches\n");
//                 idx_ok = 1;
//             } else {
//                 trigger_alarm("Index file hash mismatch: index may be tampered!");
//                 fprintf(stderr, "[ALERT] index_sm3 mismatch: %s (seal) vs %s (current)\n", S.index_sm3, idx_sm3_now);
//                 /* idx 被篡改，则我们不能信任 idx，后续只能 fallback 到对 audit.log 的链重算 */
//             }
//         } else {
//             fprintf(stderr, "[WARN] cannot compute sm3 of %s\n", idx_path);
//         }
//     } else {
//         fprintf(stderr, "[WARN] index_sm3 not present or idx file missing\n");
//     }

//     /* 2) 如果 idx_ok，再核对 idx 的最后一行 chash 是否等于 seal.chain，i 是否等于 seal.records */
//     int idx_tail_ok = 0;
//     if (idx_ok) {
//         unsigned long long idx_last_i = 0;
//         char idx_last_chash[65] = {0};
//         if (read_idx_tail(idx_path, &idx_last_i, idx_last_chash) == 0) {
//             if (idx_last_i == seal_records && strcmp(idx_last_chash, S.chain) == 0) {
//                 fprintf(stderr, "[DEBUG] idx tail matches seal: i=%llu chash=%s\n", idx_last_i, idx_last_chash);
//                 idx_tail_ok = 1;
//             } else {
//                 trigger_alarm("Seal/Index mismatch: records or chain differ!");
//                 fprintf(stderr, "[ALERT] idx tail vs seal mismatch: idx_i=%llu seal_records=%llu; idx_chash=%s vs seal_chain=%s\n",
//                         idx_last_i, seal_records, idx_last_chash, S.chain);
//             }
//         } else {
//             fprintf(stderr, "[WARN] cannot read idx tail\n");
//         }
//     }

//     /* 3) 如果 idx 完整可信且 tail 匹配 seal.chain，我们就直接验签通过（不再硬性要求重算 audit.log 的链也相同） */
//     int chain_trusted_by_idx = (idx_ok && idx_tail_ok);

//     /* 4) 构造 canonical 并验签（与 seal 保持一致） */
//     size_t cert_len = 0;
//     unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) diee("cert b64 decode failed");
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) diee("d2i_X509 failed");
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) diee("X509_get_pubkey failed");
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey) { trigger_alarm("Public key is not EC key!"); diee("pub is not EC"); }
//     const EC_GROUP *grp = EC_KEY_get0_group(eckey);
//     if (!grp || EC_GROUP_get_curve_name(grp) != NID_sm2) { trigger_alarm("Certificate is not SM2 type!"); diee("[ERROR] 证书不是SM2"); }

//     /* 计算 cert 指纹 */
//     unsigned char *der2 = NULL;
//     int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0) diee("i2d_X509 failed");
//     unsigned char md[EVP_MAX_MD_SIZE]; unsigned int mdlen = 0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) diee("ctx");
//     if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, md, &mdlen) != 1) {
//         EVP_MD_CTX_free(ctx); OPENSSL_free(der2); diee("fingerprint digest failed");
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump_buf(md, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         trigger_alarm("Certificate fingerprint mismatch detected!");
//         diee("[FAIL] key_fpr mismatch");
//     }

//     char *canon = build_canonical(&S);
//     if (!canon) diee("asprintf");
//     dump_hex_prefix("verify", (const unsigned char *)canon, strlen(canon));
//     size_t siglen = 0;
//     unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) diee("sig b64 decode failed");
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig);
//     free(canon);

//     if (v != 1) {
//         EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//         if (v == 0) { trigger_alarm("Signature mismatch detected!"); diee("[FAIL] signature invalid"); }
//         else        { trigger_alarm("Signature verification error!"); diee("[FAIL] verify error"); }
//     }

//     /* 验签 OK 到这里 */
//     if (chain_trusted_by_idx) {
//         fprintf(stderr, "[OK] verify success (by idx+seal)\n");

//         /* 做日志 vs idx 的逐行比对（使用 normalize） */
//         BadList bl; badlist_init(&bl);
//         unsigned long long scanned2 = 0;
//         unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, seal_records, &scanned2);
//         if (bad == (unsigned long long)-1) {
//             fprintf(stderr, "[WARN] cannot compare audit.log vs idx (skip)\n");
//         } else if (bad == seal_records && seal_records > 0) {
//             /* 全部不匹配——这极可能是 ENRICHED/RAW 格式差异或 idx 与 log 字符序列不同 */
//             fprintf(stderr, "[WARN] All %llu lines differ between audit.log and idx lhash.\n", seal_records);
//             fprintf(stderr, "       This usually means auditd writes ENRICHED logs while plugin hashes RAW (stdin).\n");
//             fprintf(stderr, "       To make byte-identical, set in /etc/audit/auditd.conf: log_format = RAW, then restart auditd.\n");

//             /* 额外诊断：输出前 1 行（若有）的实际 normalized bytes 的 hash 与 idx 中 lhash 以便定位差异 */
//             if (seal_records > 0) {
//                 FILE *lf = fopen(S.path, "rb");
//                 FILE *ix = fopen(idx_path, "r");
//                 if (lf && ix) {
//                     char *line = NULL; size_t cap=0; ssize_t n;
//                     char ibuf[1024];
//                     if ((n = getline(&line, &cap, lf)) != -1) {
//                         size_t normlen = normalize_line(line, (size_t)n);
//                         const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//                         if (sm3) {
//                             EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//                             unsigned char mdv[32]; unsigned int mdvlen=0;
//                             if (ctx2 && EVP_DigestInit_ex(ctx2, sm3, NULL) == 1 && EVP_DigestUpdate(ctx2, line, normlen) == 1 && EVP_DigestFinal_ex(ctx2, mdv, &mdvlen) == 1 && mdvlen==32) {
//                                 char mdhex[65]; hexdump_buf(mdv, 32, mdhex, sizeof(mdhex));
//                                 /* read first idx line */
//                                 while (fgets(ibuf, sizeof(ibuf), ix)) { if (ibuf[0]=='#') continue; break; }
//                                 char idx_lhex[128]={0}, idx_chex[128]={0}; unsigned long long ii=0, off=0;
//                                 if (sscanf(ibuf, "i=%llu off=%llu lhash=%64s chash=%64s", &ii, &off, idx_lhex, idx_chex) == 4) {
//                                     fprintf(stderr, "[DIAG] first-line normalized sm3 (computed now) = %s\n", mdhex);
//                                     fprintf(stderr, "[DIAG] first-line lhash from idx = %s\n", idx_lhex);
//                                 }
//                             }
//                             if (ctx2) EVP_MD_CTX_free(ctx2);
//                         }
//                     }
//                     if (line) free(line);
//                     fclose(lf);
//                     fclose(ix);
//                 }
//             }
//         } else if (bad > 0) {
//             fprintf(stderr, "[ALARM] Found %llu mismatched lines (suspect tampering). First few: ", bad);
//             size_t max_show = bl.count < 20 ? bl.count : 20;
//             for (size_t i=0;i<max_show;i++) fprintf(stderr, "%llu%s", bl.lines[i], (i+1<max_show)?",":"");
//             fputc('\n', stderr);
//             char msg[4096]; size_t pos=0;
//             pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
//             for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//             trigger_alarm(msg);
//         } else {
//             fprintf(stderr, "[DEBUG] audit.log bytes match idx for first %llu lines\n", scanned2);
//         }
//         badlist_free(&bl);

//         if (log_lines > seal_records) {
//             fprintf(stderr, "[WARN] audit.log has %llu lines but seal covers only %llu records; last %llu lines are not covered by this seal\n",
//                     log_lines, seal_records, log_lines - seal_records);
//         }

//         EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//         return 0;
//     }

//     /* 走到这里说明：idx_sm3 或 idx_tail 不匹配，我们无法信赖 idx。
//        回退：用 audit.log 的前 seal_records 行重算 chain，与 seal.chain 比较 */
//     if (seal_records > 0) {
//         unsigned char now_chain_bin[32]; unsigned long long scanned = 0;
//         if (sm3_chain_file_limit(S.path, seal_records, now_chain_bin, &scanned) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//             diee("[ERR] failed to compute chain over first N lines");
//         }
//         char now_chain_hex[65]; hexdump_buf(now_chain_bin, 32, now_chain_hex, sizeof(now_chain_hex));
//         if (strcmp(now_chain_hex, S.chain) != 0) {
//             trigger_alarm("Chain mismatch detected: audit log may be tampered!");
//             fprintf(stderr, "[FAIL] chain mismatch (文件被改动?)\n");
//             fprintf(stderr, "[DEBUG] recomputed chain (first %llu lines) = %s\n", seal_records, now_chain_hex);

//             /* 尝试用 idx 找差异（尽管 idx_sm3 不匹配） */
//             if (access(idx_path, R_OK) == 0) {
//                 BadList bl; badlist_init(&bl);
//                 unsigned long long scanned2 = 0;
//                 unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, seal_records, &scanned2);
//                 if (bad != (unsigned long long)-1) {
//                     fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned2, bad);
//                     if (bl.count > 0) {
//                         fprintf(stderr, "[DETAIL] bad line list: ");
//                         for (size_t i=0;i<bl.count;i++) fprintf(stderr, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                         fputc('\n', stderr);
//                         char msg[4096]; size_t pos=0;
//                         pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
//                         for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                         trigger_alarm(msg);
//                     }
//                 }
//                 badlist_free(&bl);
//             }

//             EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//             return 1;
//         }
//     } else {
//         /* seal.records == 0，链为空 */
//     }

//     /* 到这里：idx 不可信，但 audit.log 的前 N 行重算链与 seal.chain 一致 + 验签过 → 成功 */
//     fprintf(stderr, "[OK] verify success (by recomputing chain from audit.log; idx untrusted)\n");
//     if (log_lines > seal_records) {
//         fprintf(stderr, "[WARN] audit.log has %llu lines but seal covers only %llu records; last %llu lines are not covered by this seal\n",
//                 log_lines, seal_records, log_lines - seal_records);
//     }
//     EVP_PKEY_free(pub); X509_free(cert);
//     gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//     return 0;
// }

//-----------------版本5.2
/* ---------- audit-verify.c (修正版：修复 idx header 回退逻辑 + 统一 normalize) ---------- */
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <string.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <sys/stat.h>
// #include <limits.h>

// #include <openssl/evp.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/err.h>

// #include "alarm.c"                 // 你已有的 trigger_alarm 实现
// #include "../../lib/gm_crypto.h"   // gm_sm2_verify

// #define SM2_ID "1234567812345678"
// static const char HEX_CHARS[] = "0123456789abcdef";

// /* ----------------- 小工具 ----------------- */

// static void diee(const char *m)
// {
//     fprintf(stderr, "%s\n", m);
//     exit(1);
// }

// static void hexdump_buf(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     if (outlen < len*2 + 1) return;
//     for (size_t i = 0; i < len; i++) {
//         out[i*2]     = HEX_CHARS[(buf[i] >> 4) & 0xF];
//         out[i*2 + 1] = HEX_CHARS[buf[i] & 0xF];
//     }
//     out[len*2] = 0;
// }

// /* base64 decode (no NL handling) */
// static unsigned char *b64_decode(const char *b64, size_t *out_len)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *out_len = (size_t)n;
//     return buf;
// }

// /* normalize line: trim trailing CR/LF, return normalized length */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) len--;
//     return len;
// }

// /* 计算文件 SM3 -> hex (整文件) */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump_buf(md, 32, out_hex, 65);
//     return 0;
// }

// /* 统计文件行数 */
// static int count_lines(const char *path, unsigned long long *out_lines)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1) ln++;
//     free(line);
//     fclose(f);
//     *out_lines = ln;
//     return 0;
// }

// /* 计算文件前 N 行的 chain（SM3），同时输出实际扫描行数（<= N）
//    使用 normalize_line 与插件保持一致。 */
// static int sm3_chain_file_limit(const char *path, unsigned long long limit_lines, unsigned char out32[32], unsigned long long *out_scanned)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;

//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1) {
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, line, normlen) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         unsigned int mdlen = 0;
//         if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     if (out_scanned) *out_scanned = scanned;
//     return 0;
// }

// /* ----------------- Seal 解析 ----------------- */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// static void trimnl(char *s) {
//     size_t n = strlen(s);
//     while (n && (s[n-1]=='\n' || s[n-1]=='\r')) s[--n] = 0;
// }
// static char *trimws(char *s) {
//     while (*s == ' ' || *s == '\t') s++;
//     size_t n = strlen(s);
//     while (n > 0 && (s[n-1] == ' ' || s[n-1] == '\t' || s[n-1] == '\r' || s[n-1] == '\n')) s[--n]=0;
//     return s;
// }

// static int parse_seal(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq+1);
//         fprintf(stderr, "[DEBUG] Parsing: '%s' = '%s'\n", k, v);
//         if (strcmp(k,"version")==0) S->version = strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash = strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign = strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id = strdup(v);
//         else if (strcmp(k,"path")==0) S->path = strdup(v);
//         else if (strcmp(k,"records")==0) S->records = strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain = strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr = strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64 = strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64 = strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3 = strdup(v);
//     }
//     free(line);
//     fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
//         return 0;
//     return -1;
// }

// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }

// /* 构造 canonical 用于验签（与 seal/producer 保持一致） */
// static char *build_canonical(const struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//         "version=%s\n"
//         "hash=%s\n"
//         "sign=%s\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%s\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
//     return buf;
// }

// static void dump_hex_prefix(const char *tag, const unsigned char *buf, size_t len)
// {
//     size_t n = len > 64 ? 64 : len;
//     fprintf(stderr, "[DBG] %s canon len=%zu hex:", tag, len);
//     for (size_t i=0;i<n;i++) fprintf(stderr,"%02x",buf[i]);
//     if (len>n) fprintf(stderr,"...");
//     fputc('\n', stderr);
// }

// /* ----------------- 读取 idx 尾部 ----------------- */
// static int read_idx_tail(const char *idx_path, unsigned long long *out_i, char chash_hex[65])
// {
//     FILE *f = fopen(idx_path, "r");
//     if (!f) return -1;
//     char buf[1024];
//     unsigned long long last_i = 0;
//     char last_chash[65] = {0};

//     while (fgets(buf, sizeof(buf), f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long i=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%64s chash=%64s", &i, &off, lhex, chex) == 4) {
//             last_i = i;
//             strncpy(last_chash, chex, sizeof(last_chash)-1);
//         }
//     }
//     fclose(f);
//     if (last_i == 0) return -1;
//     if (out_i) *out_i = last_i;
//     strncpy(chash_hex, last_chash, 65);
//     return 0;
// }

// /* ----------------- 索引比较（带行限制） ----------------- */
// typedef struct {
//     unsigned long long *lines;
//     size_t count, cap;
// } BadList;
// static void badlist_init(BadList *b) { b->lines = NULL; b->count = b->cap = 0; }
// static void badlist_free(BadList *b) { free(b->lines); b->lines = NULL; b->count = b->cap = 0; }
// static void badlist_add(BadList *b, unsigned long long ln) {
//     if (!b) return;
//     if (b->count == b->cap) {
//         size_t nc = b->cap ? b->cap * 2 : 64;
//         unsigned long long *p = realloc(b->lines, nc * sizeof(*p));
//         if (!p) return;
//         b->lines = p; b->cap = nc;
//     }
//     b->lines[b->count++] = ln;
// }

// static unsigned long long compare_with_index_limit(const char *log_path, const char *idx_path,
//                                                    BadList *out_bad,
//                                                    unsigned long long max_lines,
//                                                    unsigned long long *scanned_lines)
// {
//     if (scanned_lines) *scanned_lines = 0;
//     if (out_bad) badlist_init(out_bad);
//     FILE *lf = fopen(log_path,"rb");
//     FILE *ix = fopen(idx_path,"r");
//     if (!lf || !ix) { if (lf) fclose(lf); if (ix) fclose(ix); return (unsigned long long)-1; }

//     /* 跳过 idx 头，直到 i= （修正：保证找到 i= 行时，无论位置都回退到该行起点） */
//     char ibuf[1024];
//     long back_pos = -1;
//     while (fgets(ibuf, sizeof(ibuf), ix)) {
//         if (ibuf[0] == '#') continue;
//         if (strncmp(ibuf, "i=", 2) == 0) {
//             back_pos = (long)ftell(ix) - (long)strlen(ibuf);
//             break;
//         }
//     }
//     if (back_pos >= 0) {
//         if (fseek(ix, back_pos, SEEK_SET) != 0) {
//             /* fallback: rewind to start */
//             rewind(ix);
//         }
//     } else {
//         /* no i= found: rewind to start to attempt reading from beginning */
//         rewind(ix);
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { fclose(lf); fclose(ix); return (unsigned long long)-1; }

//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, bad = 0;
//     static const char hex[] = "0123456789abcdef";

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         if (max_lines != 0 && ln > max_lines) break;

//         if (!fgets(ibuf, sizeof(ibuf), ix)) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//             continue;
//         }
//         unsigned long long idx_ln=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(ibuf, "i=%llu off=%llu lhash=%64s chash=%64s", &idx_ln, &off, lhex, chex) != 4 || idx_ln != ln) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//             continue;
//         }

//         /* normalize same as plugin */
//         size_t normlen = normalize_line(line, (size_t)n);

//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         int ok1 = ctx &&
//                   EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1 &&
//                   EVP_DigestFinal_ex(ctx, L, &lm) == 1 && lm == 32;
//         if (!ok1) {
//             if (ctx) EVP_MD_CTX_free(ctx);
//             fclose(lf); fclose(ix); free(line);
//             return (unsigned long long)-1;
//         }
//         EVP_MD_CTX_free(ctx);
//         char Lhex[65];
//         for (int i=0;i<32;i++) { Lhex[i*2] = hex[(L[i] >> 4) & 0xF]; Lhex[i*2+1] = hex[L[i] & 0xF]; }
//         Lhex[64]=0;
//         if (strcmp(Lhex, lhex) != 0) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//         }
//     }

//     free(line);
//     fclose(lf);
//     fclose(ix);
//     if (scanned_lines) *scanned_lines = (max_lines == 0) ? ln : (ln > max_lines ? max_lines : ln);
//     return bad;
// }

// /* ----------------- main ----------------- */

// int main(int argc, char **argv)
// {
//     if (argc != 2) {
//         fprintf(stderr, "Usage: %s /var/log/audit/audit.log.seal\n", argv[0]);
//         return 1;
//     }

//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();
//     if (gm_crypto_init() != 0) diee("gm init failed");
//     if (!EVP_get_digestbyname("sm3")) diee("[ERROR] 无 sm3");

//     struct seal S;
//     if (parse_seal(argv[1], &S) != 0) diee("parse seal failed");

//     if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0) diee("[ERROR] seal 非 sm2/sm3");
//     if (strcmp(S.sm2_id, SM2_ID) != 0) diee("[ERROR] SM2_ID 不匹配");

//     /* 解析 seal 中的 records */
//     unsigned long long seal_records = 0;
//     if (S.records) seal_records = strtoull(S.records, NULL, 10);

//     /* 读取 audit.log 实际行数（仅用于提示，不作为唯一依据） */
//     unsigned long long log_lines = 0;
//     if (count_lines(S.path, &log_lines) != 0) {
//         trigger_alarm("Cannot open audit log");
//         diee("[ERROR] cannot count lines of audit.log");
//     }
//     fprintf(stderr, "[DEBUG] audit.log has %llu lines; seal.records=%llu\n", log_lines, seal_records);

//     /* idx path */
//     char *idx_path = NULL;
//     if (asprintf(&idx_path, "%s.idx", S.path) < 0) idx_path = NULL;
//     if (!idx_path) diee("OOM");

//     /* 1) 先对比 index_sm3：确保 idx 未被篡改 */
//     int idx_ok = 0;
//     if (S.index_sm3 && *S.index_sm3 && access(idx_path, R_OK) == 0) {
//         char idx_sm3_now[65];
//         if (sm3_file_hex(idx_path, idx_sm3_now) == 0) {
//             if (strcmp(idx_sm3_now, S.index_sm3) == 0) {
//                 fprintf(stderr, "[DEBUG] index_sm3 matches\n");
//                 idx_ok = 1;
//             } else {
//                 trigger_alarm("Index file hash mismatch: index may be tampered!");
//                 fprintf(stderr, "[ALERT] index_sm3 mismatch: %s (seal) vs %s (current)\n", S.index_sm3, idx_sm3_now);
//                 /* idx 被篡改，则我们不能信任 idx，后续只能 fallback 到对 audit.log 的链重算 */
//             }
//         } else {
//             fprintf(stderr, "[WARN] cannot compute sm3 of %s\n", idx_path);
//         }
//     } else {
//         fprintf(stderr, "[WARN] index_sm3 not present or idx file missing\n");
//     }

//     /* 2) 如果 idx_ok，再核对 idx 的最后一行 chash 是否等于 seal.chain，i 是否等于 seal.records */
//     int idx_tail_ok = 0;
//     if (idx_ok) {
//         unsigned long long idx_last_i = 0;
//         char idx_last_chash[65] = {0};
//         if (read_idx_tail(idx_path, &idx_last_i, idx_last_chash) == 0) {
//             if (idx_last_i == seal_records && strcmp(idx_last_chash, S.chain) == 0) {
//                 fprintf(stderr, "[DEBUG] idx tail matches seal: i=%llu chash=%s\n", idx_last_i, idx_last_chash);
//                 idx_tail_ok = 1;
//             } else {
//                 trigger_alarm("Seal/Index mismatch: records or chain differ!");
//                 fprintf(stderr, "[ALERT] idx tail vs seal mismatch: idx_i=%llu seal_records=%llu; idx_chash=%s vs seal_chain=%s\n",
//                         idx_last_i, seal_records, idx_last_chash, S.chain);
//             }
//         } else {
//             fprintf(stderr, "[WARN] cannot read idx tail\n");
//         }
//     }

//     /* 3) 如果 idx 完整可信且 tail 匹配 seal.chain，我们就直接验签通过（不再硬性要求重算 audit.log 的链也相同） */
//     int chain_trusted_by_idx = (idx_ok && idx_tail_ok);

//     /* 4) 构造 canonical 并验签（与 seal 保持一致） */
//     size_t cert_len = 0;
//     unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) diee("cert b64 decode failed");
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) diee("d2i_X509 failed");
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) diee("X509_get_pubkey failed");
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey) { trigger_alarm("Public key is not EC key!"); diee("pub is not EC"); }
//     const EC_GROUP *grp = EC_KEY_get0_group(eckey);
//     if (!grp || EC_GROUP_get_curve_name(grp) != NID_sm2) { trigger_alarm("Certificate is not SM2 type!"); diee("[ERROR] 证书不是SM2"); }

//     /* 计算 cert 指纹 */
//     unsigned char *der2 = NULL;
//     int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0) diee("i2d_X509 failed");
//     unsigned char md[EVP_MAX_MD_SIZE]; unsigned int mdlen = 0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) diee("ctx");
//     if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, md, &mdlen) != 1) {
//         EVP_MD_CTX_free(ctx); OPENSSL_free(der2); diee("fingerprint digest failed");
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump_buf(md, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         trigger_alarm("Certificate fingerprint mismatch detected!");
//         diee("[FAIL] key_fpr mismatch");
//     }

//     char *canon = build_canonical(&S);
//     if (!canon) diee("asprintf");
//     dump_hex_prefix("verify", (const unsigned char *)canon, strlen(canon));
//     size_t siglen = 0;
//     unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) diee("sig b64 decode failed");
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig);
//     free(canon);

//     if (v != 1) {
//         EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//         if (v == 0) { trigger_alarm("Signature mismatch detected!"); diee("[FAIL] signature invalid"); }
//         else        { trigger_alarm("Signature verification error!"); diee("[FAIL] verify error"); }
//     }

//     /* 验签 OK 到这里 */
//     if (chain_trusted_by_idx) {
//         fprintf(stderr, "[OK] verify success (by idx+seal)\n");

//         /* 做日志 vs idx 的逐行比对（使用 normalize） */
//         BadList bl; badlist_init(&bl);
//         unsigned long long scanned2 = 0;
//         unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, seal_records, &scanned2);
//         if (bad == (unsigned long long)-1) {
//             fprintf(stderr, "[WARN] cannot compare audit.log vs idx (skip)\n");
//         } else if (bad == seal_records && seal_records > 0) {
//             /* 全部不匹配——这极可能是 ENRICHED/RAW 格式差异或 idx 与 log 字符序列不同 */
//             fprintf(stderr, "[WARN] All %llu lines differ between audit.log and idx lhash.\n", seal_records);
//             fprintf(stderr, "       This usually means auditd writes ENRICHED logs while plugin hashes RAW (stdin).\n");
//             fprintf(stderr, "       To make byte-identical, set in /etc/audit/auditd.conf: log_format = RAW, then restart auditd.\n");

//             /* 额外诊断：输出前 1 行（若有）的实际 normalized bytes 的 hash 与 idx 中 lhash 以便定位差异 */
//             if (seal_records > 0) {
//                 FILE *lf = fopen(S.path, "rb");
//                 FILE *ix = fopen(idx_path, "r");
//                 if (lf && ix) {
//                     char *line = NULL; size_t cap=0; ssize_t n;
//                     char ibuf[1024];
//                     if ((n = getline(&line, &cap, lf)) != -1) {
//                         size_t normlen = normalize_line(line, (size_t)n);
//                         const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//                         if (sm3) {
//                             EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//                             unsigned char mdv[32]; unsigned int mdvlen=0;
//                             if (ctx2 && EVP_DigestInit_ex(ctx2, sm3, NULL) == 1 && EVP_DigestUpdate(ctx2, line, normlen) == 1 && EVP_DigestFinal_ex(ctx2, mdv, &mdvlen) == 1 && mdvlen==32) {
//                                 char mdhex[65]; hexdump_buf(mdv, 32, mdhex, sizeof(mdhex));
//                                 /* read first idx line */
//                                 while (fgets(ibuf, sizeof(ibuf), ix)) { if (ibuf[0]=='#') continue; break; }
//                                 char idx_lhex[128]={0}, idx_chex[128]={0}; unsigned long long ii=0, off=0;
//                                 if (sscanf(ibuf, "i=%llu off=%llu lhash=%64s chash=%64s", &ii, &off, idx_lhex, idx_chex) == 4) {
//                                     fprintf(stderr, "[DIAG] first-line normalized sm3 (computed now) = %s\n", mdhex);
//                                     fprintf(stderr, "[DIAG] first-line lhash from idx = %s\n", idx_lhex);
//                                 }
//                             }
//                             if (ctx2) EVP_MD_CTX_free(ctx2);
//                         }
//                     }
//                     if (line) free(line);
//                     fclose(lf);
//                     fclose(ix);
//                 }
//             }
//         } else if (bad > 0) {
//             fprintf(stderr, "[ALARM] Found %llu mismatched lines (suspect tampering). First few: ", bad);
//             size_t max_show = bl.count < 20 ? bl.count : 20;
//             for (size_t i=0;i<max_show;i++) fprintf(stderr, "%llu%s", bl.lines[i], (i+1<max_show)?",":"");
//             fputc('\n', stderr);
//             char msg[4096]; size_t pos=0;
//             pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
//             for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//             trigger_alarm(msg);
//         } else {
//             fprintf(stderr, "[DEBUG] audit.log bytes match idx for first %llu lines\n", scanned2);
//         }
//         badlist_free(&bl);

//         if (log_lines > seal_records) {
//             fprintf(stderr, "[WARN] audit.log has %llu lines but seal covers only %llu records; last %llu lines are not covered by this seal\n",
//                     log_lines, seal_records, log_lines - seal_records);
//         }

//         EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//         return 0;
//     }

//     /* 走到这里说明：idx_sm3 或 idx_tail 不匹配，我们无法信赖 idx。
//        回退：用 audit.log 的前 seal_records 行重算 chain，与 seal.chain 比较 */
//     if (seal_records > 0) {
//         unsigned char now_chain_bin[32]; unsigned long long scanned = 0;
//         if (sm3_chain_file_limit(S.path, seal_records, now_chain_bin, &scanned) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//             diee("[ERR] failed to compute chain over first N lines");
//         }
//         char now_chain_hex[65]; hexdump_buf(now_chain_bin, 32, now_chain_hex, sizeof(now_chain_hex));
//         if (strcmp(now_chain_hex, S.chain) != 0) {
//             trigger_alarm("Chain mismatch detected: audit log may be tampered!");
//             fprintf(stderr, "[FAIL] chain mismatch (文件被改动?)\n");
//             fprintf(stderr, "[DEBUG] recomputed chain (first %llu lines) = %s\n", seal_records, now_chain_hex);

//             /* 尝试用 idx 找差异（尽管 idx_sm3 不匹配） */
//             if (access(idx_path, R_OK) == 0) {
//                 BadList bl; badlist_init(&bl);
//                 unsigned long long scanned2 = 0;
//                 unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, seal_records, &scanned2);
//                 if (bad != (unsigned long long)-1) {
//                     fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned2, bad);
//                     if (bl.count > 0) {
//                         fprintf(stderr, "[DETAIL] bad line list: ");
//                         for (size_t i=0;i<bl.count;i++) fprintf(stderr, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                         fputc('\n', stderr);
//                         char msg[4096]; size_t pos=0;
//                         pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
//                         for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                         trigger_alarm(msg);
//                     }
//                 }
//                 badlist_free(&bl);
//             }

//             EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//             return 1;
//         }
//     } else {
//         /* seal.records == 0，链为空 */
//     }

//     /* 到这里：idx 不可信，但 audit.log 的前 N 行重算链与 seal.chain 一致 + 验签过 → 成功 */
//     fprintf(stderr, "[OK] verify success (by recomputing chain from audit.log; idx untrusted)\n");
//     if (log_lines > seal_records) {
//         fprintf(stderr, "[WARN] audit.log has %llu lines but seal covers only %llu records; last %llu lines are not covered by this seal\n",
//                 log_lines, seal_records, log_lines - seal_records);
//     }
//     EVP_PKEY_free(pub); X509_free(cert);
//     gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//     return 0;
// }

// 版本6（---暂用）
//通过 “密封文件（.seal）+ 索引文件（.idx）+ 审计日志（如 audit.log）” 的三重校验
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <string.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <sys/stat.h>
// #include <limits.h>

// #include <openssl/evp.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/err.h>

// #include "alarm.c"                 // trigger_alarm
// #include "../../lib/gm_crypto.h"   // gm_sm2_verify

// #define SM2_ID "1234567812345678"
// static const char HEX_CHARS[] = "0123456789abcdef";

// /* ----------------- 小工具 ----------------- */
// static void diee(const char *m)
// {
//     fprintf(stderr, "%s\n", m);
//     exit(1);
// }

// //二进制数据转十六进制字符串
// static void hexdump_buf(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     if (outlen < len*2 + 1) return;
//     for (size_t i = 0; i < len; i++) {
//         out[i*2]     = HEX_CHARS[(buf[i] >> 4) & 0xF];
//         out[i*2 + 1] = HEX_CHARS[buf[i] & 0xF];
//     }
//     out[len*2] = 0;
// }

// //将 Base64 编码字符串（如.seal中的cert_b64证书、sig_b64签名）转为二进制数据
// static unsigned char *b64_decode(const char *b64, size_t *out_len)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *out_len = (size_t)n;
//     return buf;
// }

// //日志行格式化：去除字符串末尾的\n或\r
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) len--;
//     return len;
// }

// //计算文件的 SM3 哈希（十六进制）
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump_buf(md, 32, out_hex, 65);
//     return 0;
// }

// //统计文件行数
// static int count_lines(const char *path, unsigned long long *out_lines)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1) ln++;
//     free(line);
//     fclose(f);
//     *out_lines = ln;
//     return 0;
// }

// //计算日志前 N 行的链式 SM3 哈希
// static int sm3_chain_file_limit(const char *path, unsigned long long limit_lines, unsigned char out32[32], unsigned long long *out_scanned)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;

//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1) {
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, line, normlen) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         unsigned int mdlen = 0;
//         if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     if (out_scanned) *out_scanned = scanned;
//     return 0;
// }

// //存储seal文件的所有字段
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// //去除字符串末尾的\n或\r
// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n' || s[n-1]=='\r')) s[--n] = 0; }
// //去除字符串首尾的空白字符
// static char *trimws(char *s) {
//     while (*s == ' ' || *s == '\t') s++;
//     size_t n = strlen(s);
//     while (n > 0 && (s[n-1] == ' ' || s[n-1] == '\t' || s[n-1] == '\r' || s[n-1] == '\n')) s[--n]=0;
//     return s;
// }
// //解析.seal文件
// static int parse_seal(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version = strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash = strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign = strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id = strdup(v);
//         else if (strcmp(k,"path")==0) S->path = strdup(v);
//         else if (strcmp(k,"records")==0) S->records = strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain = strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr = strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64 = strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64 = strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3 = strdup(v);
//     }
//     free(line);
//     fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
//         return 0;
//     return -1;
// }
// //释放struct seal中动态分配的字段
// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// //构建 规范数据字符串
// static char *build_canonical(const struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//         "version=%s\n"
//         "hash=%s\n"
//         "sign=%s\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%s\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
//     return buf;
// }
// //读取索引文件（.idx）的最后一行：解析最后一行的行号（i）和链式哈希（chash）
// static int read_idx_tail(const char *idx_path, unsigned long long *out_i, char chash_hex[65])
// {
//     FILE *f = fopen(idx_path, "r");
//     if (!f) return -1;
//     char buf[1024];
//     unsigned long long last_i = 0;
//     char last_chash[65] = {0};

//     while (fgets(buf, sizeof(buf), f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long i=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%64s chash=%64s", &i, &off, lhex, chex) == 4) {
//             last_i = i;
//             strncpy(last_chash, chex, sizeof(last_chash)-1);
//         }
//     }
//     fclose(f);
//     if (last_i == 0) return -1;
//     if (out_i) *out_i = last_i;
//     strncpy(chash_hex, last_chash, 65);
//     return 0;
// }

// //动态存储篡改行号
// typedef struct {
//     unsigned long long *lines; //lines为行号数组
//     size_t count, cap; //count为实际篡改行数, cap为数组容量（动态扩容）
// } BadList;

// static void badlist_init(BadList *b) { b->lines = NULL; b->count = b->cap = 0; }
// static void badlist_free(BadList *b) { free(b->lines); b->lines = NULL; b->count = b->cap = 0; }
// static void badlist_add(BadList *b, unsigned long long ln) {
//     if (!b) return;
//     if (b->count == b->cap) {
//         size_t nc = b->cap ? b->cap * 2 : 64;
//         unsigned long long *p = realloc(b->lines, nc * sizeof(*p));
//         if (!p) return;
//         b->lines = p; b->cap = nc;
//     }
//     b->lines[b->count++] = ln;
// }

// //逐行对比审计日志与索引文件读取log_path（日志）和idx_path（索引），
// //对比前max_lines行的行哈希（lhash），将不匹配的行号存入out_bad，返回篡改行数
// static unsigned long long compare_with_index_limit(const char *log_path, const char *idx_path,
//                                                    BadList *out_bad,
//                                                    unsigned long long max_lines,
//                                                    unsigned long long *scanned_lines)
// {
//     if (scanned_lines) *scanned_lines = 0;
//     if (out_bad) badlist_init(out_bad);
//     FILE *lf = fopen(log_path,"rb");
//     FILE *ix = fopen(idx_path,"r");
//     if (!lf || !ix) { if (lf) fclose(lf); if (ix) fclose(ix); return (unsigned long long)-1; }

//     /* 定位 idx 第一个 i= 行 */
//     char ibuf[1024];
//     long back_pos = -1;
//     while (fgets(ibuf, sizeof(ibuf), ix)) {
//         if (ibuf[0] == '#') continue;
//         if (strncmp(ibuf, "i=", 2) == 0) {
//             back_pos = (long)ftell(ix) - (long)strlen(ibuf);
//             break;
//         }
//     }
//     if (back_pos >= 0) {
//         if (fseek(ix, back_pos, SEEK_SET) != 0) {
//             rewind(ix);
//         }
//     } else {
//         rewind(ix);
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { fclose(lf); fclose(ix); return (unsigned long long)-1; }

//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, bad = 0;
//     static const char hex[] = "0123456789abcdef";

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         if (max_lines != 0 && ln > max_lines) break;

//         if (!fgets(ibuf, sizeof(ibuf), ix)) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//             continue;
//         }
//         unsigned long long idx_ln=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(ibuf, "i=%llu off=%llu lhash=%64s chash=%64s", &idx_ln, &off, lhex, chex) != 4 || idx_ln != ln) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//             continue;
//         }
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         int ok1 = ctx &&
//                   EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1 &&
//                   EVP_DigestFinal_ex(ctx, L, &lm) == 1 && lm == 32;
//         if (!ok1) {
//             if (ctx) EVP_MD_CTX_free(ctx);
//             fclose(lf); fclose(ix); free(line);
//             return (unsigned long long)-1;
//         }
//         EVP_MD_CTX_free(ctx);
//         char Lhex[65];
//         for (int i=0;i<32;i++) { Lhex[i*2] = hex[(L[i] >> 4) & 0xF]; Lhex[i*2+1] = hex[L[i] & 0xF]; }
//         Lhex[64]=0;
//         if (strcmp(Lhex, lhex) != 0) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//         }
//     }
//     free(line);
//     fclose(lf);
//     fclose(ix);
//     if (scanned_lines) *scanned_lines = (max_lines == 0) ? ln : (ln > max_lines ? max_lines : ln);
//     return bad;
// }

// /* ----------------- main ----------------- */
// int main(int argc, char **argv)
// {
//     if (argc != 2) {
//         fprintf(stderr, "Usage: %s /var/log/audit/audit.log.seal\n", argv[0]);
//         return 1;
//     }
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();
//     if (gm_crypto_init() != 0) diee("gm init failed");
//     if (!EVP_get_digestbyname("sm3")) diee("[ERROR] 无 sm3");

//     struct seal S;
//     if (parse_seal(argv[1], &S) != 0) diee("parse seal failed");
//     if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0) diee("[ERROR] seal 非 sm2/sm3");
//     if (strcmp(S.sm2_id, SM2_ID) != 0) diee("[ERROR] SM2_ID 不匹配");

//     unsigned long long seal_records = 0;
//     if (S.records) seal_records = strtoull(S.records, NULL, 10);

//     /* 读取 audit.log 实际行数（提示） */
//     unsigned long long log_lines = 0;
//     if (count_lines(S.path, &log_lines) != 0) {
//         trigger_alarm("Cannot open audit log");
//         diee("[ERROR] cannot count lines of audit.log");
//     }
//     fprintf(stderr, "[DEBUG] audit.log has %llu lines; seal.records=%llu\n", log_lines, seal_records);

//     char *idx_path = NULL;
//     if (asprintf(&idx_path, "%s.idx", S.path) < 0) idx_path = NULL;
//     if (!idx_path) diee("OOM");

//     /* 1) 验签：始终严格检查 */
//     size_t cert_len = 0;
//     unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) diee("cert b64 decode failed");
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) diee("d2i_X509 failed");
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) diee("X509_get_pubkey failed");
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey) { trigger_alarm("Public key is not EC key!"); diee("pub is not EC"); }
//     const EC_GROUP *grp = EC_KEY_get0_group(eckey);
//     if (!grp || EC_GROUP_get_curve_name(grp) != NID_sm2) { trigger_alarm("Certificate is not SM2 type!"); diee("[ERROR] 证书不是SM2"); }

//     unsigned char *der2 = NULL;
//     int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0) diee("i2d_X509 failed");
//     unsigned char md[EVP_MAX_MD_SIZE]; unsigned int mdlen = 0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) diee("ctx");
//     if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, md, &mdlen) != 1) {
//         EVP_MD_CTX_free(ctx); OPENSSL_free(der2); diee("fingerprint digest failed");
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump_buf(md, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         trigger_alarm("Certificate fingerprint mismatch detected!");
//         diee("[FAIL] key_fpr mismatch");
//     }
//     char *canon = build_canonical(&S);
//     if (!canon) diee("asprintf");
//     size_t siglen = 0;
//     unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) diee("sig b64 decode failed");
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig);
//     free(canon);
//     if (v != 1) {
//         EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//         if (v == 0) { trigger_alarm("Signature mismatch detected!"); diee("[FAIL] signature invalid"); }
//         else        { trigger_alarm("Signature verification error!"); diee("[FAIL] verify error"); }
//     }

//     /* 2) 必须重算 chain（日志前 records 行），与 seal.chain 比较 */
//     if (seal_records > 0) {
//         unsigned char now_chain_bin[32]; unsigned long long scannedN = 0;
//         if (sm3_chain_file_limit(S.path, seal_records, now_chain_bin, &scannedN) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//             diee("[ERR] failed to compute chain over first N lines");
//         }
//         char now_chain_hex[65]; hexdump_buf(now_chain_bin, 32, now_chain_hex, sizeof(now_chain_hex));
//         if (strcmp(now_chain_hex, S.chain) != 0) {
//             trigger_alarm("Chain mismatch detected: audit log may be tampered!");
//             fprintf(stderr, "[FAIL] chain mismatch (文件被改动?)\n");
//             fprintf(stderr, "[DEBUG] recomputed chain (first %llu lines) = %s\n", seal_records, now_chain_hex);
//             /* 可选：借助 idx 定位差异 */
//             if (access(idx_path, R_OK) == 0) {
//                 BadList bl; badlist_init(&bl);
//                 unsigned long long scanned2 = 0;
//                 unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, seal_records, &scanned2);
//                 if (bad != (unsigned long long)-1) {
//                     fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned2, bad);
//                     if (bl.count > 0) {
//                         fprintf(stderr, "[DETAIL] bad line list: ");
//                         for (size_t i=0;i<bl.count;i++) fprintf(stderr, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                         fputc('\n', stderr);
//                         char msg[4096]; size_t pos=0;
//                         pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
//                         for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                         trigger_alarm(msg);
//                     }
//                 }
//                 badlist_free(&bl);
//             }
//             EVP_PKEY_free(pub); X509_free(cert); gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//             return 1;
//         }
//     } else {
//         /* records=0 允许空链 */
//     }

//     /* 3) idx 对应检查（辅助） */
//     int idx_ok = 0, idx_tail_ok = 0;
//     if (S.index_sm3 && *S.index_sm3 && access(idx_path, R_OK) == 0) {
//         char idx_sm3_now[65];
//         if (sm3_file_hex(idx_path, idx_sm3_now) == 0) {
//             if (strcmp(idx_sm3_now, S.index_sm3) == 0) {
//                 idx_ok = 1;
//             } else {
//                 fprintf(stderr, "[ALERT] index_sm3 mismatch: idx may be tampered or replaced\n");
//                 trigger_alarm("Index file hash mismatch: index may be tampered!");
//             }
//         }
//     }
//     if (idx_ok) {
//         unsigned long long idx_last_i = 0;
//         char idx_last_chash[65] = {0};
//         if (read_idx_tail(idx_path, &idx_last_i, idx_last_chash) == 0) {
//             if (idx_last_i == strtoull(S.records,NULL,10) && strcmp(idx_last_chash, S.chain) == 0) {
//                 idx_tail_ok = 1;
//             } else {
//                 fprintf(stderr, "[ALERT] idx tail vs seal mismatch: idx_i=%llu seal_records=%s; idx_chash=%s vs seal_chain=%s\n",
//                         idx_last_i, S.records, idx_last_chash, S.chain);
//                 trigger_alarm("Seal/Index mismatch: records or chain differ!");
//             }
//         }
//     }
//     if (idx_ok && idx_tail_ok) {
//         BadList bl; badlist_init(&bl);
//         unsigned long long scanned2 = 0;
//         unsigned long long bad = compare_with_index_limit(S.path, idx_path, &bl, strtoull(S.records,NULL,10), &scanned2);
//         if (bad == (unsigned long long)-1) {
//             fprintf(stderr, "[WARN] cannot compare audit.log vs idx (skip)\n");
//         } else if (bad == strtoull(S.records,NULL,10) && strtoull(S.records,NULL,10) > 0) {
//             fprintf(stderr, "[WARN] All %s lines differ (RAW/ENRICHED mismatch suspected). Set auditd log_format=RAW.\n", S.records);
//         } else if (bad > 0) {
//             fprintf(stderr, "[ALARM] Found %llu mismatched lines. First few: ", bad);
//             size_t max_show = bl.count < 20 ? bl.count : 20;
//             for (size_t i=0;i<max_show;i++) fprintf(stderr, "%llu%s", bl.lines[i], (i+1<max_show)?",":"");
//             fputc('\n', stderr);
//             char msg[4096]; size_t pos=0;
//             pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
//             for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//             trigger_alarm(msg);
//         } else {
//             fprintf(stderr, "[DEBUG] audit.log bytes match idx for first %s lines\n", S.records);
//         }
//         badlist_free(&bl);
//     } else {
//         fprintf(stderr, "[WARN] idx not fully trusted/skipped; but chain verification passed.\n");
//     }

//     if (log_lines > strtoull(S.records,NULL,10)) {
//         fprintf(stderr, "[WARN] audit.log has %llu lines but seal covers only %s records; last %llu lines are not covered by this seal\n",
//                 log_lines, S.records, log_lines - strtoull(S.records,NULL,10));
//     }
//     fprintf(stderr, "[OK] verify success\n");

//     EVP_PKEY_free(pub); X509_free(cert);
//     gm_crypto_cleanup(); free_seal(&S); free(idx_path);
//     return 0;
// }



//版本 单文件，批量文件校验
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <string.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <sys/stat.h>
// #include <dirent.h>
// #include <errno.h>
// #include <limits.h>

// #include <openssl/evp.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/err.h>

// #include "alarm.c"                 // trigger_alarm
// #include "../../lib/gm_crypto.h"   // gm_sm2_verify

// #define SM2_ID "1234567812345678"
// static const char HEX_CHARS[] = "0123456789abcdef";

// /* ----------------- 小工具 ----------------- */
// static void diee(const char *m)
// {
//     fprintf(stderr, "%s\n", m);
//     exit(1);
// }

// static int file_exists(const char *p)
// {
//     struct stat st;
//     return (p && *p && stat(p, &st) == 0 && S_ISREG(st.st_mode));
// }

// static int is_directory(const char *p)
// {
//     struct stat st;
//     return (p && *p && stat(p, &st) == 0 && S_ISDIR(st.st_mode));
// }

// // 二进制数据转十六进制字符串
// static void hexdump_buf(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     if (outlen < len*2 + 1) return;
//     for (size_t i = 0; i < len; i++) {
//         out[i*2]     = HEX_CHARS[(buf[i] >> 4) & 0xF];
//         out[i*2 + 1] = HEX_CHARS[buf[i] & 0xF];
//     }
//     out[len*2] = 0;
// }

// // 将 Base64 编码字符串转为二进制数据
// static unsigned char *b64_decode(const char *b64, size_t *out_len)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = (unsigned char*)malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *out_len = (size_t)n;
//     return buf;
// }

// // 日志行格式化：去除末尾 \n 或 \r
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) len--;
//     return len;
// }

// // 计算文件 SM3（十六进制）
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump_buf(md, 32, out_hex, 65);
//     return 0;
// }

// // 统计文件行数
// static int count_lines(const char *path, unsigned long long *out_lines)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1) ln++;
//     free(line);
//     fclose(f);
//     *out_lines = ln;
//     return 0;
// }

// // 计算日志前 N 行的链式 SM3
// static int sm3_chain_file_limit(const char *path, unsigned long long limit_lines, unsigned char out32[32], unsigned long long *out_scanned)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;

//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1) {
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, line, normlen) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         unsigned int mdlen = 0;
//         if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     if (out_scanned) *out_scanned = scanned;
//     return 0;
// }

// // seal 结构
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// // 去除末尾 \n\r
// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n' || s[n-1]=='\r')) s[--n] = 0; }
// // 去除首尾空白
// static char *trimws(char *s) {
//     while (*s == ' ' || *s == '\t') s++;
//     size_t n = strlen(s);
//     while (n > 0 && (s[n-1] == ' ' || s[n-1] == '\t' || s[n-1] == '\r' || s[n-1] == '\n')) s[--n]=0;
//     return s;
// }
// // 解析 .seal
// static int parse_seal(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version = strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash = strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign = strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id = strdup(v);
//         else if (strcmp(k,"path")==0) S->path = strdup(v);
//         else if (strcmp(k,"records")==0) S->records = strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain = strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr = strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64 = strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64 = strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3 = strdup(v);
//     }
//     free(line);
//     fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
//         return 0;
//     return -1;
// }
// // 释放 seal
// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// // 规范数据串
// static char *build_canonical(const struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//         "version=%s\n"
//         "hash=%s\n"
//         "sign=%s\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%s\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
//     return buf;
// }

// // 读取 .idx 尾项
// static int read_idx_tail(const char *idx_path, unsigned long long *out_i, char chash_hex[65])
// {
//     FILE *f = fopen(idx_path, "r");
//     if (!f) return -1;
//     char buf[1024];
//     unsigned long long last_i = 0;
//     char last_chash[65] = {0};

//     while (fgets(buf, sizeof(buf), f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long i=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%64s chash=%64s", &i, &off, lhex, chex) == 4) {
//             last_i = i;
//             strncpy(last_chash, chex, sizeof(last_chash)-1);
//         }
//     }
//     fclose(f);
//     if (last_i == 0) return -1;
//     if (out_i) *out_i = last_i;
//     strncpy(chash_hex, last_chash, 65);
//     return 0;
// }

// // 篡改行列表
// typedef struct {
//     unsigned long long *lines;
//     size_t count, cap;
// } BadList;

// static void badlist_init(BadList *b) { b->lines = NULL; b->count = b->cap = 0; }
// static void badlist_free(BadList *b) { free(b->lines); b->lines = NULL; b->count = b->cap = 0; }
// static void badlist_add(BadList *b, unsigned long long ln) {
//     if (!b) return;
//     if (b->count == b->cap) {
//         size_t nc = b->cap ? b->cap * 2 : 64;
//         unsigned long long *p = (unsigned long long*)realloc(b->lines, nc * sizeof(*p));
//         if (!p) return;
//         b->lines = p; b->cap = nc;
//     }
//     b->lines[b->count++] = ln;
// }

// // 与 .idx 逐行比对（前 max_lines 行），返回不匹配行数
// static unsigned long long compare_with_index_limit(const char *log_path, const char *idx_path,
//                                                    BadList *out_bad,
//                                                    unsigned long long max_lines,
//                                                    unsigned long long *scanned_lines)
// {
//     if (scanned_lines) *scanned_lines = 0;
//     if (out_bad) badlist_init(out_bad);
//     FILE *lf = fopen(log_path,"rb");
//     FILE *ix = fopen(idx_path,"r");
//     if (!lf || !ix) { if (lf) fclose(lf); if (ix) fclose(ix); return (unsigned long long)-1; }

//     /* 定位 idx 第一个 i= 行（容忍开头注释） */
//     char ibuf[1024];
//     long back_pos = -1;
//     while (fgets(ibuf, sizeof(ibuf), ix)) {
//         if (ibuf[0] == '#') continue;
//         if (strncmp(ibuf, "i=", 2) == 0) {
//             back_pos = (long)ftell(ix) - (long)strlen(ibuf);
//             break;
//         }
//     }
//     if (back_pos >= 0) {
//         if (fseek(ix, back_pos, SEEK_SET) != 0) rewind(ix);
//     } else {
//         rewind(ix);
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { fclose(lf); fclose(ix); return (unsigned long long)-1; }

//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, bad = 0;
//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         if (max_lines != 0 && ln > max_lines) break;

//         if (!fgets(ibuf, sizeof(ibuf), ix)) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//             continue;
//         }
//         unsigned long long idx_ln=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(ibuf, "i=%llu off=%llu lhash=%64s chash=%64s", &idx_ln, &off, lhex, chex) != 4 || idx_ln != ln) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//             continue;
//         }
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         int ok1 = ctx &&
//                   EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1 &&
//                   EVP_DigestFinal_ex(ctx, L, &lm) == 1 && lm == 32;
//         if (!ok1) {
//             if (ctx) EVP_MD_CTX_free(ctx);
//             fclose(lf); fclose(ix); free(line);
//             return (unsigned long long)-1;
//         }
//         EVP_MD_CTX_free(ctx);
//         char Lhex[65];
//         hexdump_buf(L, 32, Lhex, sizeof(Lhex));
//         if (strcmp(Lhex, lhex) != 0) {
//             bad++;
//             if (out_bad) badlist_add(out_bad, ln);
//         }
//     }
//     free(line);
//     fclose(lf);
//     fclose(ix);
//     if (scanned_lines) *scanned_lines = (max_lines == 0) ? ln : (ln > max_lines ? max_lines : ln);
//     return bad;
// }

// /* ---------- 通过链式 SM3 自动“归位”日志文件 ---------- */
// /* 当 seal.path 不存在时，扫描其所在目录的 audit.log*，用前 N 行链式 SM3 精确匹配 */
// static int find_matching_log_by_chain(const char *seal_path, const struct seal *S, char out_log_path[PATH_MAX])
// {
//     if (!seal_path || !S || !S->chain || !S->records) return -1;
//     unsigned long long N = strtoull(S->records, NULL, 10);
//     if (N == 0) return -1;

//     /* 取目录名 */
//     char dir[PATH_MAX];
//     snprintf(dir, sizeof(dir), "%s", S->path && strchr(S->path, '/') ? S->path : "/var/log/audit/audit.log");
//     char *slash = strrchr(dir, '/');
//     if (!slash) return -1;
//     *slash = 0;
//     if (!*dir) strcpy(dir, "/");

//     DIR *d = opendir(dir);
//     if (!d) return -1;
//     struct dirent *de;
//     int ok = -1;

//     while ((de = readdir(d))) {
//         /* 只看 audit.log 或 audit.log.* */
//         if (strncmp(de->d_name, "audit.log", 9) != 0) continue;
//         char cand[PATH_MAX];
//         snprintf(cand, sizeof(cand), "%s/%s", dir, de->d_name);
//         if (!file_exists(cand)) continue;

//         unsigned char cbin[32]; char chex[65];
//         if (sm3_chain_file_limit(cand, N, cbin, NULL) != 0) continue;
//         hexdump_buf(cbin, 32, chex, sizeof(chex));
//         if (strcmp(chex, S->chain) == 0) {
//             strncpy(out_log_path, cand, PATH_MAX-1);
//             out_log_path[PATH_MAX-1] = 0;
//             ok = 0;
//             break;
//         }
//     }
//     closedir(d);
//     return ok;
// }

// /* ---------- 单个 .seal 验证主流程 ---------- */
// static int verify_one(const char *seal_path)
// {
//     fprintf(stderr, "== Verifying: %s ==\n", seal_path);

//     struct seal S;
//     if (parse_seal(seal_path, &S) != 0) {
//         trigger_alarm("Seal file is not parseable!");
//         fprintf(stderr, "[FAIL] parse seal failed: %s\n\n", seal_path);
//         return 2;
//     }
//     if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0) {
//         trigger_alarm("Seal algorithm mismatch (expect SM2/SM3)");
//         fprintf(stderr, "[ERROR] seal 非 sm2/sm3\n\n");
//         free_seal(&S);
//         return 2;
//     }
//     if (strcmp(S.sm2_id, SM2_ID) != 0) {
//         trigger_alarm("SM2_ID mismatch!");
//         fprintf(stderr, "[ERROR] SM2_ID 不匹配\n\n");
//         free_seal(&S);
//         return 2;
//     }

//     unsigned long long seal_records = 0;
//     if (S.records) seal_records = strtoull(S.records, NULL, 10);

//     /* ========== 1) 验签（严格） ========== */
//     size_t cert_len = 0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { fprintf(stderr, "[FAIL] cert b64 decode failed\n\n"); free_seal(&S); return 2; }
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) { fprintf(stderr, "[FAIL] d2i_X509 failed\n\n"); free_seal(&S); return 2; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { fprintf(stderr, "[FAIL] X509_get_pubkey failed\n\n"); X509_free(cert); free_seal(&S); return 2; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey) { trigger_alarm("Public key is not EC key!"); fprintf(stderr, "[FAIL] pub is not EC\n\n"); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2; }
//     const EC_GROUP *grp = EC_KEY_get0_group(eckey);
//     if (!grp || EC_GROUP_get_curve_name(grp) != NID_sm2) {
//         trigger_alarm("Certificate is not SM2 type!");
//         fprintf(stderr, "[ERROR] 证书不是SM2\n\n");
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         return 2;
//     }

//     unsigned char *der2 = NULL;
//     int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0) { fprintf(stderr, "[FAIL] i2d_X509 failed\n\n"); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2; }
//     unsigned char md[EVP_MAX_MD_SIZE]; unsigned int mdlen = 0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fprintf(stderr, "[FAIL] ctx alloc\n\n"); OPENSSL_free(der2); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2; }
//     if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, md, &mdlen) != 1) {
//         EVP_MD_CTX_free(ctx); OPENSSL_free(der2); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         fprintf(stderr, "[FAIL] fingerprint digest failed\n\n");
//         return 2;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump_buf(md, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         trigger_alarm("Certificate fingerprint mismatch detected!");
//         fprintf(stderr, "[FAIL] key_fpr mismatch\n\n");
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         return 2;
//     }
//     char *canon = build_canonical(&S);
//     if (!canon) { fprintf(stderr, "[FAIL] asprintf canon\n\n"); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2; }
//     size_t siglen = 0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) { free(canon); fprintf(stderr, "[FAIL] sig b64 decode failed\n\n"); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2; }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) {
//         if (v == 0) { trigger_alarm("Signature mismatch detected!"); fprintf(stderr, "[FAIL] signature invalid\n\n"); }
//         else        { trigger_alarm("Signature verification error!"); fprintf(stderr, "[FAIL] verify error\n\n"); }
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         return 2;
//     }

//     /* ========== 2) 找到真实日志路径（必要时“归位”） ========== */
//     char log_path[PATH_MAX];
//     snprintf(log_path, sizeof(log_path), "%s", S.path);
//     if (!file_exists(log_path)) {
//         char matched[PATH_MAX]={0};
//         if (find_matching_log_by_chain(seal_path, &S, matched) == 0) {
//             fprintf(stderr, "[INFO] seal.path 不存在，已定位对应日志：%s\n", matched);
//             strncpy(log_path, matched, sizeof(log_path)-1);
//             log_path[sizeof(log_path)-1] = 0;
//         } else {
//             trigger_alarm("Referenced audit log not found for this seal!");
//             fprintf(stderr, "[FAIL] referenced audit log not found: %s\n\n", S.path);
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 2;
//         }
//     }

//     /* 读取日志行数（提示） */
//     unsigned long long log_lines = 0;
//     if (count_lines(log_path, &log_lines) != 0) {
//         trigger_alarm("Cannot open audit log");
//         fprintf(stderr, "[ERROR] cannot count lines of audit log: %s\n\n", log_path);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         return 2;
//     }
//     fprintf(stderr, "[DEBUG] %s has %llu lines; seal.records=%llu\n", log_path, log_lines, seal_records);

//     /* idx 路径：与日志同名加 .idx（兼容轮转名） */
//     char *idx_path = NULL;
//     if (asprintf(&idx_path, "%s.idx", log_path) < 0) idx_path = NULL;
//     if (!idx_path) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         fprintf(stderr, "[FAIL] OOM for idx path\n\n");
//         return 2;
//     }

//     /* ========== 3) 重算链并比对 ========== */
//     if (seal_records > 0) {
//         unsigned char now_chain_bin[32]; unsigned long long scannedN = 0;
//         if (sm3_chain_file_limit(log_path, seal_records, now_chain_bin, &scannedN) != 0) {
//             fprintf(stderr, "[ERR] failed to compute chain over first N lines\n\n");
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); free(idx_path);
//             return 2;
//         }
//         char now_chain_hex[65]; hexdump_buf(now_chain_bin, 32, now_chain_hex, sizeof(now_chain_hex));
//         if (strcmp(now_chain_hex, S.chain) != 0) {
//             trigger_alarm("Chain mismatch detected: audit log may be tampered!");
//             fprintf(stderr, "[FAIL] chain mismatch (文件被改动?)\n");
//             fprintf(stderr, "[DEBUG] recomputed chain (first %llu lines) = %s\n", seal_records, now_chain_hex);

//             if (access(idx_path, R_OK) == 0) {
//                 BadList bl; badlist_init(&bl);
//                 unsigned long long scanned2 = 0;
//                 unsigned long long bad = compare_with_index_limit(log_path, idx_path, &bl, seal_records, &scanned2);
//                 if (bad != (unsigned long long)-1) {
//                     fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned2, bad);
//                     if (bl.count > 0) {
//                         fprintf(stderr, "[DETAIL] bad line list: ");
//                         for (size_t i=0;i<bl.count;i++) fprintf(stderr, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                         fputc('\n', stderr);
//                         char msg[4096]; size_t pos=0;
//                         pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
//                         for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//                         trigger_alarm(msg);
//                     }
//                 }
//                 badlist_free(&bl);
//             } else {
//                 fprintf(stderr, "[WARN] idx missing for %s; cannot list exact bad lines.\n", log_path);
//             }
//             fprintf(stderr, "\n");
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); free(idx_path);
//             return 1; // 有篡改
//         }
//     } else {
//         /* records=0 允许空链 */
//     }

//     /* ========== 4) 与 idx 的一致性检查（辅助） ========== */
//     int idx_ok = 0, idx_tail_ok = 0;
//     if (S.index_sm3 && *S.index_sm3 && access(idx_path, R_OK) == 0) {
//         char idx_sm3_now[65];
//         if (sm3_file_hex(idx_path, idx_sm3_now) == 0) {
//             if (strcmp(idx_sm3_now, S.index_sm3) == 0) {
//                 idx_ok = 1;
//             } else {
//                 fprintf(stderr, "[ALERT] index_sm3 mismatch: idx may be tampered or replaced (%s)\n", idx_path);
//                 trigger_alarm("Index file hash mismatch: index may be tampered!");
//             }
//         }
//     } else if (access(idx_path, R_OK) != 0) {
//         fprintf(stderr, "[WARN] missing idx: %s (仍可通过链式校验确认完整性)\n", idx_path);
//     }

//     if (idx_ok) {
//         unsigned long long idx_last_i = 0;
//         char idx_last_chash[65] = {0};
//         if (read_idx_tail(idx_path, &idx_last_i, idx_last_chash) == 0) {
//             if (idx_last_i == strtoull(S.records,NULL,10) && strcmp(idx_last_chash, S.chain) == 0) {
//                 idx_tail_ok = 1;
//             } else {
//                 fprintf(stderr, "[ALERT] idx tail vs seal mismatch: idx_i=%llu seal_records=%s; idx_chash=%s vs seal_chain=%s\n",
//                         idx_last_i, S.records, idx_last_chash, S.chain);
//                 trigger_alarm("Seal/Index mismatch: records or chain differ!");
//             }
//         }
//     }
//     if (idx_ok && idx_tail_ok) {
//         BadList bl; badlist_init(&bl);
//         unsigned long long scanned2 = 0;
//         unsigned long long bad = compare_with_index_limit(log_path, idx_path, &bl, strtoull(S.records,NULL,10), &scanned2);
//         if (bad == (unsigned long long)-1) {
//             fprintf(stderr, "[WARN] cannot compare audit.log vs idx (skip)\n");
//         } else if (bad == strtoull(S.records,NULL,10) && strtoull(S.records,NULL,10) > 0) {
//             fprintf(stderr, "[WARN] All %s lines differ (RAW/ENRICHED mismatch suspected). Set auditd log_format=RAW.\n", S.records);
//         } else if (bad > 0) {
//             fprintf(stderr, "[ALARM] Found %llu mismatched lines. First few: ", bad);
//             size_t max_show = bl.count < 20 ? bl.count : 20;
//             for (size_t i=0;i<max_show;i++) fprintf(stderr, "%llu%s", bl.lines[i], (i+1<max_show)?",":"");
//             fputc('\n', stderr);
//             char msg[4096]; size_t pos=0;
//             pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
//             for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
//             trigger_alarm(msg);
//         } else {
//             fprintf(stderr, "[DEBUG] %s bytes match idx for first %s lines\n", log_path, S.records);
//         }
//         badlist_free(&bl);
//     } else if (idx_ok && !idx_tail_ok) {
//         fprintf(stderr, "[WARN] idx tail 不一致，尽管链式校验通过；请检查 idx 生成流程。\n");
//     } else {
//         fprintf(stderr, "[INFO] 未使用 idx 逐行比对（仅链式校验已通过）。\n");
//     }

//     if (log_lines > strtoull(S.records,NULL,10)) {
//         fprintf(stderr, "[WARN] %s 有 %llu 行，而 seal 覆盖 %s 行；末尾 %llu 行不在本次封印范围内\n",
//                 log_path, log_lines, S.records, log_lines - strtoull(S.records,NULL,10));
//     }
//     fprintf(stderr, "[OK] verify success\n\n");

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); free(idx_path);
//     return 0;
// }

// /* ----------------- main ----------------- */
// int main(int argc, char **argv)
// {
//     if (argc != 2) {
//         fprintf(stderr, "Usage:\n  %s /var/log/audit/audit.log.seal\n  %s /var/log/audit/   (目录，批量验证 *.seal)\n", argv[0], argv[0]);
//         return 1;
//     }
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();
//     if (gm_crypto_init() != 0) diee("gm init failed");
//     if (!EVP_get_digestbyname("sm3")) diee("[ERROR] 无 sm3");

//     int rc = 0;

//     if (is_directory(argv[1])) {
//         /* 扫描目录内 *.seal */
//         DIR *d = opendir(argv[1]);
//         if (!d) diee("cannot open directory");
//         struct dirent *de;
//         char path[PATH_MAX];
//         while ((de = readdir(d))) {
//             size_t n = strlen(de->d_name);
//             if (n >= 5 && strcmp(de->d_name + (n - 5), ".seal") == 0) {
//                 snprintf(path, sizeof(path), "%s/%s", argv[1], de->d_name);
//                 int one = verify_one(path);
//                 if (one != 0) rc = 1; /* 目录模式：只要有一个失败则返回非 0 */
//             }
//         }
//         closedir(d);
//     } else {
//         rc = verify_one(argv[1]);
//     }

//     gm_crypto_cleanup();
//     return rc;
// }



//版本---解决篡改后出发审计覆盖导致审计不出来篡改行问题
// audit-verify.c
// *** COMPLETE FILE ***

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>

#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/bio.h>
#include <openssl/err.h>

#include "alarm.c"
#include "../../lib/gm_crypto.h"

#define SM2_ID "1234567812345678"
static const char HEX_CHARS[] = "0123456789abcdef"

;

// 工具函数略（与你版本一致）——开始
static void diee(const char *m)
{
    fprintf(stderr, "%s\n", m);
    exit(1);
}
static int file_exists(const char *p)
{
    struct stat st;
    return (p && *p && stat(p, &st) == 0 && S_ISREG(st.st_mode));
}
static int is_directory(const char *p)
{
    struct stat st;
    return (p && *p && stat(p, &st) == 0 && S_ISDIR(st.st_mode));
}
static void hexdump_buf(const unsigned char *buf, size_t len, char *out, size_t outlen)
{
    if (outlen < len*2 + 1) return;
    for (size_t i = 0; i < len; i++) {
        out[i*2]     = HEX_CHARS[(buf[i] >> 4) & 0xF];
        out[i*2 + 1] = HEX_CHARS[buf[i] & 0xF];
    }
    out[len*2] = 0;
}
static unsigned char *b64_decode(const char *b64, size_t *out_len)
{
    BIO *b = BIO_new(BIO_f_base64());
    BIO *m = BIO_new_mem_buf(b64, -1);
    if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
#ifdef BIO_FLAGS_BASE64_NO_NL
    BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
#endif
    BIO_push(b, m);
    size_t cap = strlen(b64) * 3 / 4 + 16;
    unsigned char *buf = (unsigned char*)malloc(cap);
    int n = BIO_read(b, buf, (int)cap);
    BIO_free_all(b);
    if (n <= 0) { free(buf); return NULL; }
    *out_len = (size_t)n;
    return buf;
}
static size_t normalize_line(char *buf, size_t len)
{
    while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) len--;
    return len;
}
static int sm3_file_hex(const char *path, char out_hex[65])
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;
    FILE *f = fopen(path, "rb");
    if (!f) return -1;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (!ctx) { fclose(f); return -1; }
    if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
    unsigned char buf[4096];
    size_t n;
    while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
        if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
    }
    fclose(f);
    unsigned char md[32]; unsigned int mdlen = 0;
    if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
    EVP_MD_CTX_free(ctx);
    hexdump_buf(md, 32, out_hex, 65);
    return 0;
}
static int count_lines(const char *path, unsigned long long *out_lines)
{
    FILE *f = fopen(path, "rb");
    if (!f) return -1;
    char *line = NULL; size_t cap = 0; ssize_t n;
    unsigned long long ln = 0;
    while ((n = getline(&line, &cap, f)) != -1) ln++;
    free(line);
    fclose(f);
    *out_lines = ln;
    return 0;
}
static int sm3_chain_file_limit(const char *path, unsigned long long limit_lines, unsigned char out32[32], unsigned long long *out_scanned)
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;
    unsigned char H[32] = {0};
    unsigned long long scanned = 0;

    FILE *fp = fopen(path, "rb");
    if (!fp) return -1;
    char *line = NULL; size_t cap = 0; ssize_t n;
    while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1) {
        size_t normlen = normalize_line(line, (size_t)n);
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        if (!ctx) { fclose(fp); free(line); return -1; }
        if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
        if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
        if (EVP_DigestUpdate(ctx, line, normlen) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
        unsigned int mdlen = 0;
        if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
        EVP_MD_CTX_free(ctx);
        scanned++;
    }
    free(line);
    fclose(fp);
    memcpy(out32, H, 32);
    if (out_scanned) *out_scanned = scanned;
    return 0;
}
struct seal {
    char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
};
static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n' || s[n-1]=='\r')) s[--n] = 0; }
static char *trimws(char *s) {
    while (*s == ' ' || *s == '\t') s++;
    size_t n = strlen(s);
    while (n > 0 && (s[n-1] == ' ' || s[n-1] == '\t' || s[n-1] == '\r' || s[n-1] == '\n')) s[--n]=0;
    return s;
}
static int parse_seal(const char *seal_path, struct seal *S)
{
    memset(S, 0, sizeof(*S));
    FILE *f = fopen(seal_path, "r");
    if (!f) return -1;
    char *line = NULL; size_t cap = 0; ssize_t n;
    while ((n = getline(&line, &cap, f)) != -1) {
        if (line[0] == '#' || line[0] == 0) continue;
        trimnl(line);
        char *eq = strchr(line, '=');
        if (!eq) continue;
        *eq = 0;
        char *k = trimws(line);
        char *v = trimws(eq+1);
        if (strcmp(k,"version")==0) S->version = strdup(v);
        else if (strcmp(k,"hash")==0) S->hash = strdup(v);
        else if (strcmp(k,"sign")==0) S->sign = strdup(v);
        else if (strcmp(k,"sm2_id")==0) S->sm2_id = strdup(v);
        else if (strcmp(k,"path")==0) S->path = strdup(v);
        else if (strcmp(k,"records")==0) S->records = strdup(v);
        else if (strcmp(k,"chain")==0) S->chain = strdup(v);
        else if (strcmp(k,"key_fpr")==0) S->key_fpr = strdup(v);
        else if (strcmp(k,"cert_der_base64")==0) S->cert_b64 = strdup(v);
        else if (strcmp(k,"sig_base64")==0) S->sig_b64 = strdup(v);
        else if (strcmp(k,"index_sm3")==0) S->index_sm3 = strdup(v);
    }
    free(line);
    fclose(f);
    if (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
        return 0;
    return -1;
}
static void free_seal(struct seal *S)
{
    free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
    free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
    free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
}
static char *build_canonical(const struct seal *S)
{
    char *buf = NULL;
    if (asprintf(&buf,
        "version=%s\n"
        "hash=%s\n"
        "sign=%s\n"
        "sm2_id=%s\n"
        "path=%s\n"
        "records=%s\n"
        "chain=%s\n"
        "key_fpr=%s\n"
        "index_sm3=%s\n",
        S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
    return buf;
}
static int read_idx_tail(const char *idx_path, unsigned long long *out_i, char chash_hex[65])
{
    FILE *f = fopen(idx_path, "r");
    if (!f) return -1;
    char buf[1024];
    unsigned long long last_i = 0;
    char last_chash[65] = {0};

    while (fgets(buf, sizeof(buf), f)) {
        if (buf[0] == '#') continue;
        unsigned long long i=0, off=0;
        char lhex[128]={0}, chex[128]={0};
        if (sscanf(buf, "i=%llu off=%llu lhash=%64s chash=%64s", &i, &off, lhex, chex) == 4) {
            last_i = i;
            strncpy(last_chash, chex, sizeof(last_chash)-1);
        }
    }
    fclose(f);
    if (last_i == 0) return -1;
    if (out_i) *out_i = last_i;
    strncpy(chash_hex, last_chash, 65);
    return 0;
}
typedef struct {
    unsigned long long *lines;
    size_t count, cap;
} BadList;
static void badlist_init(BadList *b) { b->lines = NULL; b->count = b->cap = 0; }
static void badlist_free(BadList *b) { free(b->lines); b->lines = NULL; b->count = b->cap = 0; }
static void badlist_add(BadList *b, unsigned long long ln) {
    if (!b) return;
    if (b->count == b->cap) {
        size_t nc = b->cap ? b->cap * 2 : 64;
        unsigned long long *p = (unsigned long long*)realloc(b->lines, nc * sizeof(*p));
        if (!p) return;
        b->lines = p; b->cap = nc;
    }
    b->lines[b->count++] = ln;
}
static unsigned long long compare_with_index_limit(const char *log_path, const char *idx_path,
                                                   BadList *out_bad,
                                                   unsigned long long max_lines,
                                                   unsigned long long *scanned_lines)
{
    if (scanned_lines) *scanned_lines = 0;
    if (out_bad) badlist_init(out_bad);
    FILE *lf = fopen(log_path,"rb");
    FILE *ix = fopen(idx_path,"r");
    if (!lf || !ix) { if (lf) fclose(lf); if (ix) fclose(ix); return (unsigned long long)-1; }

    char ibuf[1024];
    long back_pos = -1;
    while (fgets(ibuf, sizeof(ibuf), ix)) {
        if (ibuf[0] == '#') continue;
        if (strncmp(ibuf, "i=", 2) == 0) {
            back_pos = (long)ftell(ix) - (long)strlen(ibuf);
            break;
        }
    }
    if (back_pos >= 0) {
        if (fseek(ix, back_pos, SEEK_SET) != 0) rewind(ix);
    } else {
        rewind(ix);
    }

    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) { fclose(lf); fclose(ix); return (unsigned long long)-1; }

    char *line = NULL; size_t cap = 0; ssize_t n;
    unsigned long long ln = 0, bad = 0;
    while ((n = getline(&line, &cap, lf)) != -1) {
        ln++;
        if (max_lines != 0 && ln > max_lines) break;

        if (!fgets(ibuf, sizeof(ibuf), ix)) {
            bad++;
            if (out_bad) badlist_add(out_bad, ln);
            continue;
        }
        unsigned long long idx_ln=0, off=0;
        char lhex[128]={0}, chex[128]={0};
        if (sscanf(ibuf, "i=%llu off=%llu lhash=%64s chash=%64s", &idx_ln, &off, lhex, chex) != 4 || idx_ln != ln) {
            bad++;
            if (out_bad) badlist_add(out_bad, ln);
            continue;
        }
        size_t normlen = normalize_line(line, (size_t)n);
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        unsigned char L[32]; unsigned int lm=0;
        int ok1 = ctx &&
                  EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
                  EVP_DigestUpdate(ctx, line, normlen) == 1 &&
                  EVP_DigestFinal_ex(ctx, L, &lm) == 1 && lm == 32;
        if (!ok1) {
            if (ctx) EVP_MD_CTX_free(ctx);
            fclose(lf); fclose(ix); free(line);
            return (unsigned long long)-1;
        }
        EVP_MD_CTX_free(ctx);
        char Lhex[65];
        hexdump_buf(L, 32, Lhex, sizeof(Lhex));
        if (strcmp(Lhex, lhex) != 0) {
            bad++;
            if (out_bad) badlist_add(out_bad, ln);
        }
    }
    free(line);
    fclose(lf);
    fclose(ix);
    if (scanned_lines) *scanned_lines = (max_lines == 0) ? ln : (ln > max_lines ? max_lines : ln);
    return bad;
}
// 工具函数略（与你版本一致）——结束

static int find_matching_log_by_chain(const char *seal_path, const struct seal *S, char out_log_path[PATH_MAX])
{
    if (!seal_path || !S || !S->chain || !S->records) return -1;
    unsigned long long N = strtoull(S->records, NULL, 10);
    if (N == 0) return -1;

    char dir[PATH_MAX];
    snprintf(dir, sizeof(dir), "%s", S->path && strchr(S->path, '/') ? S->path : "/var/log/audit/audit.log");
    char *slash = strrchr(dir, '/');
    if (!slash) return -1;
    *slash = 0;
    if (!*dir) strcpy(dir, "/");

    DIR *d = opendir(dir);
    if (!d) return -1;
    struct dirent *de;
    int ok = -1;

    while ((de = readdir(d))) {
        if (strncmp(de->d_name, "audit.log", 9) != 0) continue;
        char cand[PATH_MAX];
        snprintf(cand, sizeof(cand), "%s/%s", dir, de->d_name);
        if (!file_exists(cand)) continue;

        unsigned char cbin[32]; char chex[65];
        if (sm3_chain_file_limit(cand, N, cbin, NULL) != 0) continue;
        hexdump_buf(cbin, 32, chex, sizeof(chex));
        if (strcmp(chex, S->chain) == 0) {
            strncpy(out_log_path, cand, PATH_MAX-1);
            out_log_path[PATH_MAX-1] = 0;
            ok = 0;
            break;
        }
    }
    closedir(d);
    return ok;
}

static int verify_one(const char *seal_path)
{
    fprintf(stderr, "== Verifying: %s ==\n", seal_path);

    struct seal S;
    if (parse_seal(seal_path, &S) != 0) {
        trigger_alarm("Seal file is not parseable!");
        fprintf(stderr, "[FAIL] parse seal failed: %s\n\n", seal_path);
        return 2;
    }
    if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0) {
        trigger_alarm("Seal algorithm mismatch (expect SM2/SM3)");
        fprintf(stderr, "[ERROR] seal 非 sm2/sm3\n\n");
        free_seal(&S);
        return 2;
    }
    if (strcmp(S.sm2_id, SM2_ID) != 0) {
        trigger_alarm("SM2_ID mismatch!");
        fprintf(stderr, "[ERROR] SM2_ID 不匹配\n\n");
        free_seal(&S);
        return 2;
    }

    unsigned long long seal_records = 0;
    if (S.records) seal_records = strtoull(S.records, NULL, 10);

    size_t cert_len = 0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
    if (!cert_der) { fprintf(stderr, "[FAIL] cert b64 decode failed\n\n"); free_seal(&S); return 2; }
    const unsigned char *p = cert_der;
    X509 *cert = d2i_X509(NULL, &p, cert_len);
    free(cert_der);
    if (!cert) { fprintf(stderr, "[FAIL] d2i_X509 failed\n\n"); free_seal(&S); return 2; }
    EVP_PKEY *pub = X509_get_pubkey(cert);
    if (!pub) { fprintf(stderr, "[FAIL] X509_get_pubkey failed\n\n"); X509_free(cert); free_seal(&S); return 2; }

    int type = EVP_PKEY_id(pub);
    if (type != EVP_PKEY_EC && type != EVP_PKEY_SM2) {
        trigger_alarm("Public key is not EC/SM2 key!");
        fprintf(stderr, "[FAIL] pub is not EC/SM2 (type=%d)\n\n", type);
        EVP_PKEY_free(pub);
        X509_free(cert);
        free_seal(&S);
        return 2;
    }
    fprintf(stderr, "[INFO] 公钥类型: %s\n", (type == EVP_PKEY_SM2) ? "SM2" : "EC");

    // EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
    // if (!eckey) { trigger_alarm("Public key is not EC key!"); fprintf(stderr, "[FAIL] pub is not EC\n\n"); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2; }
    // const EC_GROUP *grp = EC_KEY_get0_group(eckey);
    // if (!grp || EC_GROUP_get_curve_name(grp) != NID_sm2) {
    //     trigger_alarm("Certificate is not SM2 type!");
    //     fprintf(stderr, "[ERROR] 证书不是SM2\n\n");
    //     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
    //     return 2;
    // }

    unsigned char *der2 = NULL;
    int der2len = i2d_X509(cert, &der2);
    if (der2len <= 0) { fprintf(stderr, "[FAIL] i2d_X509 failed\n\n"); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2; }
    unsigned char md[EVP_MAX_MD_SIZE]; unsigned int mdlen = 0;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (!ctx) { fprintf(stderr, "[FAIL] ctx alloc\n\n"); OPENSSL_free(der2); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2; }
    if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
        EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
        EVP_DigestFinal_ex(ctx, md, &mdlen) != 1) {
        EVP_MD_CTX_free(ctx); OPENSSL_free(der2); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
        fprintf(stderr, "[FAIL] fingerprint digest failed\n\n");
        return 2;
    }
    EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
    char fpr_hex[65]; hexdump_buf(md, mdlen, fpr_hex, sizeof(fpr_hex));
    if (strcmp(fpr_hex, S.key_fpr) != 0) {
        trigger_alarm("Certificate fingerprint mismatch detected!");
        fprintf(stderr, "[FAIL] key_fpr mismatch\n\n");
        EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
        return 2;
    }
    char *canon = build_canonical(&S);
    if (!canon) { fprintf(stderr, "[FAIL] asprintf canon\n\n"); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2; }
    size_t siglen = 0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
    if (!sig) { free(canon); fprintf(stderr, "[FAIL] sig b64 decode failed\n\n"); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2; }
    int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
    free(sig); free(canon);
    if (v != 1) {
        if (v == 0) { trigger_alarm("Signature mismatch detected!"); fprintf(stderr, "[FAIL] signature invalid\n\n"); }
        else        { trigger_alarm("Signature verification error!"); fprintf(stderr, "[FAIL] verify error\n\n"); }
        EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
        return 2;
    }

    char log_path[PATH_MAX];
    snprintf(log_path, sizeof(log_path), "%s", S.path);
    if (!file_exists(log_path)) {
        char matched[PATH_MAX]={0};
        if (find_matching_log_by_chain(seal_path, &S, matched) == 0) {
            fprintf(stderr, "[INFO] seal.path 不存在，已定位对应日志：%s\n", matched);
            strncpy(log_path, matched, sizeof(log_path)-1);
            log_path[sizeof(log_path)-1] = 0;
        } else {
            trigger_alarm("Referenced audit log not found for this seal!");
            fprintf(stderr, "[FAIL] referenced audit log not found: %s\n\n", S.path);
            EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
            return 2;
        }
    }

    unsigned long long log_lines = 0;
    if (count_lines(log_path, &log_lines) != 0) {
        trigger_alarm("Cannot open audit log");
        fprintf(stderr, "[ERROR] cannot count lines of audit log: %s\n\n", log_path);
        EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
        return 2;
    }
    fprintf(stderr, "[DEBUG] %s has %llu lines; seal.records=%llu\n", log_path, log_lines, seal_records);

    char *idx_path = NULL;
    if (asprintf(&idx_path, "%s.idx", log_path) < 0) idx_path = NULL;
    if (!idx_path) {
        EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
        fprintf(stderr, "[FAIL] OOM for idx path\n\n");
        return 2;
    }

    if (seal_records > 0) {
        unsigned char now_chain_bin[32]; unsigned long long scannedN = 0;
        if (sm3_chain_file_limit(log_path, seal_records, now_chain_bin, &scannedN) != 0) {
            fprintf(stderr, "[ERR] failed to compute chain over first N lines\n\n");
            EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); free(idx_path);
            return 2;
        }
        char now_chain_hex[65]; hexdump_buf(now_chain_bin, 32, now_chain_hex, sizeof(now_chain_hex));
        if (strcmp(now_chain_hex, S.chain) != 0) {
            trigger_alarm("Chain mismatch detected: audit log may be tampered!");
            fprintf(stderr, "[FAIL] chain mismatch (文件被改动?)\n");
            fprintf(stderr, "[DEBUG] recomputed chain (first %llu lines) = %s\n", seal_records, now_chain_hex);

            if (access(idx_path, R_OK) == 0) {
                BadList bl; badlist_init(&bl);
                unsigned long long scanned2 = 0;
                unsigned long long bad = compare_with_index_limit(log_path, idx_path, &bl, seal_records, &scanned2);
                if (bad != (unsigned long long)-1) {
                    fprintf(stderr, "[DETAIL] scanned lines=%llu, bad lines=%llu\n", scanned2, bad);
                    if (bl.count > 0) {
                        fprintf(stderr, "[DETAIL] bad line list: ");
                        for (size_t i=0;i<bl.count;i++) fprintf(stderr, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
                        fputc('\n', stderr);
                        char msg[4096]; size_t pos=0;
                        pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
                        for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
                        trigger_alarm(msg);
                    }
                }
                badlist_free(&bl);
            } else {
                fprintf(stderr, "[WARN] idx missing for %s; cannot list exact bad lines.\n", log_path);
            }
            fprintf(stderr, "\n");
            EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); free(idx_path);
            return 1;
        }
    }

    int idx_ok = 0, idx_tail_ok = 0;
    if (S.index_sm3 && *S.index_sm3 && access(idx_path, R_OK) == 0) {
        char idx_sm3_now[65];
        if (sm3_file_hex(idx_path, idx_sm3_now) == 0) {
            if (strcmp(idx_sm3_now, S.index_sm3) == 0) {
                idx_ok = 1;
            } else {
                fprintf(stderr, "[ALERT] index_sm3 mismatch: idx may be tampered or replaced (%s)\n", idx_path);
                trigger_alarm("Index file hash mismatch: index may be tampered!");
            }
        }
    } else if (access(idx_path, R_OK) != 0) {
        fprintf(stderr, "[WARN] missing idx: %s (仍可通过链式校验确认完整性)\n", idx_path);
    }

    if (idx_ok) {
        unsigned long long idx_last_i = 0;
        char idx_last_chash[65] = {0};
        if (read_idx_tail(idx_path, &idx_last_i, idx_last_chash) == 0) {
            if (idx_last_i == strtoull(S.records,NULL,10) && strcmp(idx_last_chash, S.chain) == 0) {
                idx_tail_ok = 1;
            } else {
                fprintf(stderr, "[ALERT] idx tail vs seal mismatch: idx_i=%llu seal_records=%s; idx_chash=%s vs seal_chain=%s\n",
                        idx_last_i, S.records, idx_last_chash, S.chain);
                trigger_alarm("Seal/Index mismatch: records or chain differ!");
            }
        }
    }
    if (idx_ok && idx_tail_ok) {
        BadList bl; badlist_init(&bl);
        unsigned long long scanned2 = 0;
        unsigned long long bad = compare_with_index_limit(log_path, idx_path, &bl, strtoull(S.records,NULL,10), &scanned2);
        if (bad == (unsigned long long)-1) {
            fprintf(stderr, "[WARN] cannot compare audit.log vs idx (skip)\n");
        } else if (bad == strtoull(S.records,NULL,10) && strtoull(S.records,NULL,10) > 0) {
            fprintf(stderr, "[WARN] All %s lines differ (RAW/ENRICHED mismatch suspected). Set auditd log_format=RAW.\n", S.records);
        } else if (bad > 0) {
            fprintf(stderr, "[ALARM] Found %llu mismatched lines. First few: ", bad);
            size_t max_show = bl.count < 20 ? bl.count : 20;
            for (size_t i=0;i<max_show;i++) fprintf(stderr, "%llu%s", bl.lines[i], (i+1<max_show)?",":"");
            fputc('\n', stderr);
            char msg[4096]; size_t pos=0;
            pos += snprintf(msg+pos, sizeof(msg)-pos, "Tampered lines: ");
            for (size_t i=0;i<bl.count && pos < sizeof(msg)-32; i++) pos += snprintf(msg+pos, sizeof(msg)-pos, "%llu%s", bl.lines[i], (i+1<bl.count)?",":"");
            trigger_alarm(msg);
        } else {
            fprintf(stderr, "[DEBUG] %s bytes match idx for first %s lines\n", log_path, S.records);
        }
        badlist_free(&bl);
    } else if (idx_ok && !idx_tail_ok) {
        fprintf(stderr, "[WARN] idx tail 不一致，尽管链式校验通过；请检查 idx 生成流程。\n");
    } else {
        fprintf(stderr, "[INFO] 未使用 idx 逐行比对（仅链式校验已通过）。\n");
    }

    if (log_lines > strtoull(S.records,NULL,10)) {
        fprintf(stderr, "[WARN] %s 有 %llu 行，而 seal 覆盖 %s 行；末尾 %llu 行不在本次封印范围内\n",
                log_path, log_lines, S.records, log_lines - strtoull(S.records,NULL,10));
    }
    fprintf(stderr, "[OK] verify success\n\n");

    EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); free(idx_path);
    return 0;
}

int main(int argc, char **argv)
{
    if (argc != 2) {
        fprintf(stderr, "Usage:\n  %s /var/log/audit/audit.log.seal\n  %s /var/log/audit/   (目录，批量验证 *.seal)\n", argv[0], argv[0]);
        return 1;
    }
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    if (gm_crypto_init() != 0) diee("gm init failed");
    if (!EVP_get_digestbyname("sm3")) diee("[ERROR] 无 sm3");

    int rc = 0;

    if (is_directory(argv[1])) {
        DIR *d = opendir(argv[1]);
        if (!d) diee("cannot open directory");
        struct dirent *de;
        char path[PATH_MAX];
        while ((de = readdir(d))) {
            size_t n = strlen(de->d_name);
            if (n >= 5 && strcmp(de->d_name + (n - 5), ".seal") == 0) {
                snprintf(path, sizeof(path), "%s/%s", argv[1], de->d_name);
                int one = verify_one(path);
                if (one != 0) rc = 1;
            }
        }
        closedir(d);
    } else {
        rc = verify_one(argv[1]);
    }

    gm_crypto_cleanup();
    return rc;
}
